UI: PKI Read Role Details (#17985)
This commit is contained in:
parent
d392754914
commit
1c0b2df8f1
|
@ -25,3 +25,6 @@
|
||||||
/package-lock.json.ember-try
|
/package-lock.json.ember-try
|
||||||
/yarn.lock.ember-try
|
/yarn.lock.ember-try
|
||||||
/tests/helpers/vault-keys.js
|
/tests/helpers/vault-keys.js
|
||||||
|
|
||||||
|
# typescript declaration files
|
||||||
|
*.d.ts
|
||||||
|
|
|
@ -51,4 +51,8 @@ export default class PkiRoleAdapter extends ApplicationAdapter {
|
||||||
query(store, type, query) {
|
query(store, type, query) {
|
||||||
return this.fetchByQuery(store, query);
|
return this.fetchByQuery(store, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryRecord(store, type, query) {
|
||||||
|
return this.fetchByQuery(store, query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default class PkiRoleModel extends Model {
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
label: 'Issuer reference',
|
label: 'Issuer reference',
|
||||||
|
detailsLabel: 'Issuer',
|
||||||
defaultValue: 'default',
|
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.`,
|
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({
|
@attr({
|
||||||
label: 'Not valid after',
|
label: 'Not valid after',
|
||||||
|
detailsLabel: 'Issued certificates expire after',
|
||||||
subText:
|
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.',
|
'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',
|
editType: 'yield',
|
||||||
|
@ -44,6 +46,7 @@ export default class PkiRoleModel extends Model {
|
||||||
|
|
||||||
@attr({
|
@attr({
|
||||||
label: 'Backdate validity',
|
label: 'Backdate validity',
|
||||||
|
detailsLabel: 'Issued certificate backdating',
|
||||||
helperTextEnabled:
|
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.',
|
'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',
|
editType: 'ttl',
|
||||||
|
@ -57,6 +60,7 @@ export default class PkiRoleModel extends Model {
|
||||||
helperTextDisabled:
|
helperTextDisabled:
|
||||||
'The maximum Time-To-Live of certificates generated by this role. If not set, the system max lease TTL will be used.',
|
'The maximum Time-To-Live of certificates generated by this role. If not set, the system max lease TTL will be used.',
|
||||||
editType: 'ttl',
|
editType: 'ttl',
|
||||||
|
defaultShown: 'System default',
|
||||||
})
|
})
|
||||||
maxTtl;
|
maxTtl;
|
||||||
|
|
||||||
|
@ -71,6 +75,7 @@ export default class PkiRoleModel extends Model {
|
||||||
|
|
||||||
@attr('boolean', {
|
@attr('boolean', {
|
||||||
label: 'Do not store certificates in storage backend',
|
label: 'Do not store certificates in storage backend',
|
||||||
|
detailsLabel: 'Store in storage backend', // template reverses value
|
||||||
subText:
|
subText:
|
||||||
'This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked.',
|
'This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked.',
|
||||||
editType: 'boolean',
|
editType: 'boolean',
|
||||||
|
@ -80,6 +85,7 @@ export default class PkiRoleModel extends Model {
|
||||||
|
|
||||||
@attr('boolean', {
|
@attr('boolean', {
|
||||||
label: 'Basic constraints valid for non-CA',
|
label: 'Basic constraints valid for non-CA',
|
||||||
|
detailsLabel: 'Add basic constraints',
|
||||||
subText: 'Mark Basic Constraints valid when issuing non-CA certificates.',
|
subText: 'Mark Basic Constraints valid when issuing non-CA certificates.',
|
||||||
editType: 'boolean',
|
editType: 'boolean',
|
||||||
})
|
})
|
||||||
|
@ -231,16 +237,20 @@ export default class PkiRoleModel extends Model {
|
||||||
defaultValue() {
|
defaultValue() {
|
||||||
return ['DigitalSignature', 'KeyAgreement', 'KeyEncipherment'];
|
return ['DigitalSignature', 'KeyAgreement', 'KeyEncipherment'];
|
||||||
},
|
},
|
||||||
|
defaultShown: 'None',
|
||||||
})
|
})
|
||||||
keyUsage;
|
keyUsage;
|
||||||
|
|
||||||
@attr('array', {
|
@attr('array', {
|
||||||
defaultValue() {
|
defaultShown: 'None',
|
||||||
return [];
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
extKeyUsage;
|
extKeyUsage;
|
||||||
|
|
||||||
|
@attr('array', {
|
||||||
|
defaultShown: 'None',
|
||||||
|
})
|
||||||
|
extKeyUsageOids;
|
||||||
|
|
||||||
@attr({ hideFormSection: true }) organization;
|
@attr({ hideFormSection: true }) organization;
|
||||||
@attr({ hideFormSection: true }) country;
|
@attr({ hideFormSection: true }) country;
|
||||||
@attr({ hideFormSection: true }) locality;
|
@attr({ hideFormSection: true }) locality;
|
||||||
|
@ -332,7 +342,7 @@ export default class PkiRoleModel extends Model {
|
||||||
'Key parameters': ['keyType', 'keyBits', 'signatureBits'],
|
'Key parameters': ['keyType', 'keyBits', 'signatureBits'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'Key usage': ['keyUsage', 'extKeyUsage'],
|
'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'],
|
||||||
},
|
},
|
||||||
{ 'Policy identifiers': ['policyIdentifiers'] },
|
{ 'Policy identifiers': ['policyIdentifiers'] },
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<PageHeader as |p|>
|
||||||
|
<p.top>
|
||||||
|
{{! TODO: This should be replaced with HDS::Breadcrumbs }}
|
||||||
|
<nav class="key-value-header breadcrumb" aria-label="breadcrumbs" data-test-breadcrumbs="role-details">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<LinkToExternal @route="secrets">secrets</LinkToExternal>
|
||||||
|
</li>
|
||||||
|
{{#each this.breadcrumbs as |breadcrumb|}}
|
||||||
|
<li>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
{{#if breadcrumb.path}}
|
||||||
|
<LinkTo @route={{breadcrumb.path}}>
|
||||||
|
{{breadcrumb.label}}
|
||||||
|
</LinkTo>
|
||||||
|
{{else}}
|
||||||
|
{{breadcrumb.label}}
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</p.top>
|
||||||
|
<p.levelLeft>
|
||||||
|
<h1 class="title is-3" data-test-role-details-title>
|
||||||
|
<Icon @name="file-text" @size="24" class="has-text-grey-light" />
|
||||||
|
PKI Role
|
||||||
|
<code>{{@role.name}}</code>
|
||||||
|
</h1>
|
||||||
|
</p.levelLeft>
|
||||||
|
</PageHeader>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarActions>
|
||||||
|
<ConfirmAction
|
||||||
|
@buttonClasses="toolbar-link"
|
||||||
|
@onConfirmAction={{this.deleteRole}}
|
||||||
|
@confirmTitle="Delete role?"
|
||||||
|
@confirmButtonText="Delete"
|
||||||
|
data-test-pki-role-delete
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</ConfirmAction>
|
||||||
|
<div class="toolbar-separator"></div>
|
||||||
|
<LinkTo class="toolbar-link" @route="overview">Generate Certificate <Icon @name="chevron-right" /></LinkTo>
|
||||||
|
<LinkTo class="toolbar-link" @route="overview">Sign Certificate <Icon @name="chevron-right" /></LinkTo>
|
||||||
|
<LinkTo class="toolbar-link" @route="roles.role.edit" @model={{@role.id}}>Edit <Icon @name="chevron-right" /></LinkTo>
|
||||||
|
</ToolbarActions>
|
||||||
|
</Toolbar>
|
||||||
|
{{#each @role.fieldGroups as |fg|}}
|
||||||
|
{{#each-in fg as |group fields|}}
|
||||||
|
{{#if (not-eq group "default")}}
|
||||||
|
<h3 class="is-size-4 has-text-semibold has-top-margin-m">{{group}}</h3>
|
||||||
|
{{/if}}
|
||||||
|
{{#each fields as |attr|}}
|
||||||
|
{{#let (get @role attr.name) as |val|}}
|
||||||
|
{{#if (eq attr.name "issuerRef")}}
|
||||||
|
<InfoTableRow
|
||||||
|
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
|
@value={{val}}
|
||||||
|
@alwaysRender={{true}}
|
||||||
|
>
|
||||||
|
<LinkTo @route="issuers.issuer.details" @model={{val}}>{{val}}</LinkTo>
|
||||||
|
</InfoTableRow>
|
||||||
|
{{else if (includes attr.name this.arrayAttrs)}}
|
||||||
|
<InfoTableRow
|
||||||
|
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
|
@value={{val}}
|
||||||
|
@alwaysRender={{true}}
|
||||||
|
>
|
||||||
|
{{#if (gt val.length 0)}}
|
||||||
|
{{#each val as |key|}}
|
||||||
|
<span>{{key}},</span>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
None
|
||||||
|
{{/if}}
|
||||||
|
</InfoTableRow>
|
||||||
|
{{else if (eq attr.name "noStore")}}
|
||||||
|
<InfoTableRow
|
||||||
|
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
|
@value={{not val}}
|
||||||
|
@alwaysRender={{true}}
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<InfoTableRow
|
||||||
|
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
|
@value={{val}}
|
||||||
|
@alwaysRender={{true}}
|
||||||
|
@formatDate={{eq attr.name "customTtl"}}
|
||||||
|
@type={{or attr.type attr.options.type}}
|
||||||
|
@defaultShown={{attr.options.defaultShown}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
|
{{/each}}
|
||||||
|
{{/each-in}}
|
||||||
|
{{/each}}
|
|
@ -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<Args> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,8 @@ export default class PkiRolesIndexRoute extends Route {
|
||||||
@service pathHelp;
|
@service pathHelp;
|
||||||
|
|
||||||
beforeModel() {
|
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');
|
return this.pathHelp.getNewModel('pki/role', 'pki');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
roles.role.details
|
<PkiRoleDetailsPage @role={{this.model}} />
|
|
@ -7,7 +7,35 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ember-cli-babel": "*",
|
"ember-cli-babel": "*",
|
||||||
"ember-cli-htmlbars": "*",
|
"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": {
|
"ember-addon": {
|
||||||
"paths": [
|
"paths": [
|
||||||
|
|
|
@ -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"]',
|
||||||
|
};
|
|
@ -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`
|
||||||
|
<PkiRoleDetailsPage @role={{this.model}} />
|
||||||
|
`,
|
||||||
|
{ 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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,17 @@
|
||||||
{
|
{
|
||||||
"extends": "@tsconfig/ember/tsconfig.json",
|
"extends": "@tsconfig/ember/tsconfig.json",
|
||||||
"compilerOptions": {
|
"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
|
// The combination of `baseUrl` with `paths` allows Ember's classic package
|
||||||
// layout, which is not resolvable with the Node resolution algorithm, to
|
// layout, which is not resolvable with the Node resolution algorithm, to
|
||||||
// work with TypeScript.
|
// work with TypeScript.
|
||||||
|
|
Loading…
Reference in New Issue