mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 10:12:35 +00:00
UI: PKI Role toolbar (#18229)
This commit is contained in:
@@ -55,4 +55,8 @@ export default class PkiRoleAdapter extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
return this.fetchByQuery(store, query);
|
||||
}
|
||||
deleteRecord(store, type, snapshot) {
|
||||
const { id, record } = snapshot;
|
||||
return this.ajax(this._urlForRole(record.backend, id), 'DELETE');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,71 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
|
||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||
import { withFormFields } from 'vault/decorators/model-form-fields';
|
||||
|
||||
const validations = {
|
||||
name: [{ type: 'presence', message: 'Name is required.' }],
|
||||
};
|
||||
|
||||
const fieldGroups = [
|
||||
{
|
||||
default: [
|
||||
'name',
|
||||
'issuerRef',
|
||||
'customTtl',
|
||||
'notBeforeDuration',
|
||||
'maxTtl',
|
||||
'generateLease',
|
||||
'noStore',
|
||||
'addBasicConstraints',
|
||||
],
|
||||
},
|
||||
{
|
||||
'Domain handling': [
|
||||
'allowedDomains',
|
||||
'allowedDomainsTemplate',
|
||||
'allowBareDomains',
|
||||
'allowSubdomains',
|
||||
'allowGlobDomains',
|
||||
'allowWildcardCertificates',
|
||||
'allowLocalhost', // default: true (returned true by OpenApi)
|
||||
'allowAnyName',
|
||||
'enforceHostnames', // default: true (returned true by OpenApi)
|
||||
],
|
||||
},
|
||||
{
|
||||
'Key parameters': ['keyType', 'keyBits', 'signatureBits'],
|
||||
},
|
||||
{
|
||||
'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'],
|
||||
},
|
||||
{ 'Policy identifiers': ['policyIdentifiers'] },
|
||||
{
|
||||
'Subject Alternative Name (SAN) Options': [
|
||||
'allowIpSans',
|
||||
'allowedUriSans',
|
||||
'allowUriSansTemplate',
|
||||
'allowedOtherSans',
|
||||
],
|
||||
},
|
||||
{
|
||||
'Additional subject fields': [
|
||||
'allowedSerialNumbers',
|
||||
'requireCn',
|
||||
'useCsrCommonName',
|
||||
'useCsrSans',
|
||||
'ou',
|
||||
'organization',
|
||||
'country',
|
||||
'locality',
|
||||
'province',
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@withFormFields(null, fieldGroups)
|
||||
@withModelValidations(validations)
|
||||
export default class PkiRoleModel extends Model {
|
||||
get useOpenAPI() {
|
||||
@@ -242,34 +300,33 @@ export default class PkiRoleModel extends Model {
|
||||
@attr({ hideFormSection: true }) postalCode;
|
||||
/* End of overriding Additional subject field options */
|
||||
|
||||
/* CAPABILITIES */
|
||||
/* CAPABILITIES
|
||||
* Default to show UI elements unless we know they can't access the given path
|
||||
*/
|
||||
@lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id') updatePath;
|
||||
get canDelete() {
|
||||
return this.updatePath.get('canCreate');
|
||||
return this.updatePath.get('isLoading') || this.updatePath.get('canCreate') !== false;
|
||||
}
|
||||
get canEdit() {
|
||||
return this.updatePath.get('canEdit');
|
||||
return this.updatePath.get('isLoading') || this.updatePath.get('canUpdate') !== false;
|
||||
}
|
||||
get canRead() {
|
||||
return this.updatePath.get('canRead');
|
||||
return this.updatePath.get('isLoading') || this.updatePath.get('canRead') !== false;
|
||||
}
|
||||
|
||||
@lazyCapabilities(apiPath`${'backend'}/issue/${'id'}`, 'backend', 'id') generatePath;
|
||||
get canReadIssue() {
|
||||
// ARG TODO was duplicate name, added Issue
|
||||
return this.generatePath.get('canUpdate');
|
||||
get canGenerateCert() {
|
||||
return this.generatePath.get('isLoading') || this.generatePath.get('canUpdate') !== false;
|
||||
}
|
||||
@lazyCapabilities(apiPath`${'backend'}/sign/${'id'}`, 'backend', 'id') signPath;
|
||||
get canSign() {
|
||||
return this.signPath.get('canUpdate');
|
||||
return this.signPath.get('isLoading') || this.signPath.get('canUpdate') !== false;
|
||||
}
|
||||
@lazyCapabilities(apiPath`${'backend'}/sign-verbatim/${'id'}`, 'backend', 'id') signVerbatimPath;
|
||||
get canSignVerbatim() {
|
||||
return this.signVerbatimPath.get('canUpdate');
|
||||
return this.signVerbatimPath.get('isLoading') || this.signVerbatimPath.get('canUpdate') !== false;
|
||||
}
|
||||
|
||||
_fieldToAttrsGroups = null;
|
||||
|
||||
// Gets header/footer copy for specific toggle groups.
|
||||
get fieldGroupsInfo() {
|
||||
return {
|
||||
@@ -297,67 +354,4 @@ export default class PkiRoleModel extends Model {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get fieldGroups() {
|
||||
if (!this._fieldToAttrsGroups) {
|
||||
this._fieldToAttrsGroups = fieldToAttrs(this, [
|
||||
{
|
||||
default: [
|
||||
'name',
|
||||
'issuerRef',
|
||||
'customTtl',
|
||||
'notBeforeDuration',
|
||||
'maxTtl',
|
||||
'generateLease',
|
||||
'noStore',
|
||||
'addBasicConstraints',
|
||||
],
|
||||
},
|
||||
{
|
||||
'Domain handling': [
|
||||
'allowedDomains',
|
||||
'allowedDomainsTemplate',
|
||||
'allowBareDomains',
|
||||
'allowSubdomains',
|
||||
'allowGlobDomains',
|
||||
'allowWildcardCertificates',
|
||||
'allowLocalhost', // default: true (returned true by OpenApi)
|
||||
'allowAnyName',
|
||||
'enforceHostnames', // default: true (returned true by OpenApi)
|
||||
],
|
||||
},
|
||||
{
|
||||
'Key parameters': ['keyType', 'keyBits', 'signatureBits'],
|
||||
},
|
||||
{
|
||||
'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'],
|
||||
},
|
||||
{ 'Policy identifiers': ['policyIdentifiers'] },
|
||||
{
|
||||
'Subject Alternative Name (SAN) Options': [
|
||||
'allowIpSans',
|
||||
'allowedUriSans',
|
||||
'allowUriSansTemplate',
|
||||
'allowedOtherSans',
|
||||
],
|
||||
},
|
||||
{
|
||||
'Additional subject fields': [
|
||||
'allowedSerialNumbers',
|
||||
'requireCn',
|
||||
'useCsrCommonName',
|
||||
'useCsrSans',
|
||||
'ou',
|
||||
'organization',
|
||||
'country',
|
||||
'locality',
|
||||
'province',
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
return this._fieldToAttrsGroups;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</form>
|
||||
{{else}}
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
{{#each this.model.fieldGroups as |fieldGroup|}}
|
||||
{{#each this.model.formFieldGroups as |fieldGroup|}}
|
||||
{{#each-in fieldGroup as |group fields|}}
|
||||
{{#if (or (eq group "default") (eq group "Options"))}}
|
||||
{{#each fields as |attr|}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{#unless this.dontShowTab}}
|
||||
{{#if @isEngine}}
|
||||
<LinkTo @route={{@link}}>
|
||||
<LinkTo @route={{@link}} data-test-secret-list-tab={{@label}}>
|
||||
{{@label}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</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>
|
||||
{{#if @role.canDelete}}
|
||||
<ConfirmAction
|
||||
@buttonClasses="toolbar-link"
|
||||
@onConfirmAction={{this.deleteRole}}
|
||||
@confirmTitle="Delete role?"
|
||||
@confirmButtonText="Delete"
|
||||
data-test-pki-role-delete
|
||||
>
|
||||
Delete
|
||||
</ConfirmAction>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{/if}}
|
||||
{{#if @role.canGenerateCert}}
|
||||
<LinkTo class="toolbar-link" @route="roles.role.generate" @model={{@role.id}} data-test-pki-role-generate-cert>
|
||||
Generate Certificate
|
||||
<Icon @name="chevron-right" />
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{#if @role.canSign}}
|
||||
<LinkTo class="toolbar-link" @route="roles.role.sign" @model={{@role.id}} data-test-pki-role-sign-cert>
|
||||
Sign Certificate
|
||||
<Icon @name="chevron-right" />
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{#if @role.canEdit}}
|
||||
<LinkTo class="toolbar-link" @route="roles.role.edit" @model={{@role.id}} data-test-pki-role-edit-link>
|
||||
Edit
|
||||
<Icon @name="chevron-right" />
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{#each @role.fieldGroups as |fg|}}
|
||||
{{#each @role.formFieldGroups 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>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { action } from '@ember/object';
|
||||
import RouterService from '@ember/routing/router-service';
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
// interface Attribute {
|
||||
// name: string;
|
||||
// options?: {
|
||||
// label?: string;
|
||||
// };
|
||||
// }
|
||||
import FlashMessageService from 'vault/services/flash-messages';
|
||||
import { inject as service } from '@ember/service';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
// TODO: pull this in from route model once it's TS
|
||||
interface Args {
|
||||
role: {
|
||||
backend: string;
|
||||
id: string;
|
||||
rollbackAttributes: () => void;
|
||||
destroyRecord: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export default class DetailsPage extends Component<Args> {
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
get breadcrumbs() {
|
||||
return [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
@@ -30,7 +32,15 @@ export default class DetailsPage extends Component<Args> {
|
||||
return ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'];
|
||||
}
|
||||
|
||||
@action deleteRole() {
|
||||
// TODO: delete role
|
||||
@action
|
||||
async deleteRole() {
|
||||
try {
|
||||
await this.args.role.destroyRecord();
|
||||
this.flashMessages.success('Role deleted successfully');
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.roles.index');
|
||||
} catch (error) {
|
||||
this.args.role.rollbackAttributes();
|
||||
this.flashMessages.danger(errorMessage(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-role-details-title>
|
||||
{{#if @model.isNew}}
|
||||
Create a PKI role
|
||||
{{else}}
|
||||
Edit
|
||||
{{@model.name}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<form {{on "submit" (perform this.save)}}>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
{{! ARG TODO write a test for namespace reminder }}
|
||||
<NamespaceReminder @mode={{if @model.isNew "create" "update"}} @noun="PKI role" />
|
||||
{{#each @model.fieldGroups as |fieldGroup|}}
|
||||
{{#each @model.formFieldGroups as |fieldGroup|}}
|
||||
{{#each-in fieldGroup as |group fields|}}
|
||||
{{! DEFAULT VIEW }}
|
||||
{{#if (eq group "default")}}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class PkiCertificatesIndexRoute extends Route {
|
||||
|
||||
beforeModel() {
|
||||
// Must call this promise before the model hook otherwise it doesn't add OpenApi to record.
|
||||
return this.pathHelp.getNewModel('pki/certificate', 'pki');
|
||||
return this.pathHelp.getNewModel('pki/certificate', this.secretMountPath.currentPath);
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class PkiIssuersIndexRoute extends Route {
|
||||
|
||||
beforeModel() {
|
||||
// Must call this promise before the model hook otherwise it doesn't add OpenApi to record.
|
||||
return this.pathHelp.getNewModel('pki/issuer', 'pki');
|
||||
return this.pathHelp.getNewModel('pki/issuer', this.secretMountPath.currentPath);
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class PkiKeysIndexRoute extends Route {
|
||||
|
||||
beforeModel() {
|
||||
// Must call this promise before the model hook otherwise it doesn't add OpenApi to record.
|
||||
return this.pathHelp.getNewModel('pki/key', 'pki');
|
||||
return this.pathHelp.getNewModel('pki/key', this.secretMountPath.currentPath);
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -12,4 +12,15 @@ export default class PkiRolesCreateRoute extends PkiRolesIndexRoute {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const backend = this.secretMountPath.currentPath || 'pki';
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: backend, route: 'overview' },
|
||||
{ label: 'roles', route: 'roles.index' },
|
||||
{ label: 'create' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class PkiRolesIndexRoute extends Route {
|
||||
beforeModel() {
|
||||
// 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', this.secretMountPath.currentPath);
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -8,4 +8,16 @@ export default class RolesRoleDetailsRoute extends PkiRolesIndexRoute {
|
||||
id: role,
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const { id } = resolvedModel;
|
||||
const backend = this.secretMountPath.currentPath || 'pki';
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: backend, route: 'overview' },
|
||||
{ label: 'roles', route: 'roles.index' },
|
||||
{ label: id },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,17 @@ export default class PkiRoleEditRoute extends PkiRolesIndexRoute {
|
||||
id: role,
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const { id } = resolvedModel;
|
||||
const backend = this.secretMountPath.currentPath || 'pki';
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: backend, route: 'overview' },
|
||||
{ label: 'roles', route: 'roles.index' },
|
||||
{ label: id, route: 'roles.role.details' },
|
||||
{ label: 'edit' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-role-page-title>
|
||||
Create a PKI role
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiRoleForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @type="add" @route="roles.create">
|
||||
<ToolbarLink @type="add" @route="roles.create" data-test-pki-role-create-link>
|
||||
Create role
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-role-page-title>
|
||||
<Icon @name="file-text" @size="24" class="has-text-grey-light" />
|
||||
PKI Role
|
||||
<code>{{this.model.name}}</code>
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
<Page::PkiRoleDetails @role={{this.model}} />
|
||||
@@ -1,3 +1,13 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-role-page-title>
|
||||
Edit role
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
<PkiRoleForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}}
|
||||
|
||||
205
ui/tests/acceptance/pki/pki-engine-workflow-test.js
Normal file
205
ui/tests/acceptance/pki/pki-engine-workflow-test.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import logout from 'vault/tests/pages/logout';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||
import { click, currentURL, fillIn, visit } from '@ember/test-helpers';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/workflow';
|
||||
|
||||
const consoleComponent = create(consoleClass);
|
||||
|
||||
const tokenWithPolicy = async function (name, policy) {
|
||||
await consoleComponent.runCommands([
|
||||
`write sys/policies/acl/${name} policy=${btoa(policy)}`,
|
||||
`write -field=client_token auth/token/create policies=${name}`,
|
||||
]);
|
||||
return consoleComponent.lastLogOutput;
|
||||
};
|
||||
|
||||
const runCommands = async function (commands) {
|
||||
try {
|
||||
await consoleComponent.runCommands(commands);
|
||||
const res = consoleComponent.lastLogOutput;
|
||||
if (res.includes('Error')) {
|
||||
throw new Error(res);
|
||||
}
|
||||
return res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`The following occurred when trying to run the command(s):\n ${commands.join('\n')} \n\n ${
|
||||
consoleComponent.lastLogOutput
|
||||
}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This test module should test the PKI workflow, including:
|
||||
* - link between pages and confirm that the url is as expected
|
||||
* - log in as user with a policy and ensure expected UI elements are shown/hidden
|
||||
*/
|
||||
module('Acceptance | pki workflow', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
await authPage.login();
|
||||
// Setup PKI engine
|
||||
const mountPath = `pki-workflow-${new Date().getTime()}`;
|
||||
await enablePage.enable('pki', mountPath);
|
||||
await runCommands([
|
||||
`write ${mountPath}/roles/some-role \
|
||||
issuer_ref="default" \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains=true \
|
||||
max_ttl="720h"`,
|
||||
]);
|
||||
this.mountPath = mountPath;
|
||||
|
||||
await logout.visit();
|
||||
});
|
||||
|
||||
hooks.afterEach(async function () {
|
||||
await logout.visit();
|
||||
await authPage.login();
|
||||
// Cleanup engine
|
||||
await runCommands([`delete sys/mounts/${this.mountPath}`]);
|
||||
await logout.visit();
|
||||
});
|
||||
|
||||
module('roles', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
await authPage.login();
|
||||
// Setup role-specific items
|
||||
await runCommands([
|
||||
`write ${this.mountPath}/roles/some-role \
|
||||
issuer_ref="default" \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains=true \
|
||||
max_ttl="720h"`,
|
||||
]);
|
||||
const pki_admin_policy = `
|
||||
path "${this.mountPath}/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
},
|
||||
`;
|
||||
const pki_reader_policy = `
|
||||
path "${this.mountPath}/roles" {
|
||||
capabilities = ["read", "list"]
|
||||
},
|
||||
path "${this.mountPath}/roles/*" {
|
||||
capabilities = ["read", "list"]
|
||||
},
|
||||
`;
|
||||
const pki_editor_policy = `
|
||||
path "${this.mountPath}/roles" {
|
||||
capabilities = ["read", "list"]
|
||||
},
|
||||
path "${this.mountPath}/roles/*" {
|
||||
capabilities = ["read", "update"]
|
||||
},
|
||||
`;
|
||||
this.pkiRoleReader = await tokenWithPolicy('pki-reader', pki_reader_policy);
|
||||
this.pkiRoleEditor = await tokenWithPolicy('pki-editor', pki_editor_policy);
|
||||
this.pkiAdminToken = await tokenWithPolicy('pki-admin', pki_admin_policy);
|
||||
await logout.visit();
|
||||
});
|
||||
|
||||
test('shows correct items if user has all permissions', async function (assert) {
|
||||
await authPage.login(this.pkiAdminToken);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.dom(SELECTORS.rolesTab).exists('Roles tab is present');
|
||||
await click(SELECTORS.rolesTab);
|
||||
assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list');
|
||||
await click('.linked-block');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
|
||||
assert.dom(SELECTORS.generateCertLink).exists('Generate cert link is shown');
|
||||
await click(SELECTORS.generateCertLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/generate`);
|
||||
|
||||
// Go back to details and test all the links
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
assert.dom(SELECTORS.signCertLink).exists('Sign cert link is shown');
|
||||
await click(SELECTORS.signCertLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/sign`);
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
assert.dom(SELECTORS.editRoleLink).exists('Edit link is shown');
|
||||
await click(SELECTORS.editRoleLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/edit`);
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
assert.dom(SELECTORS.deleteRoleButton).exists('Delete role button is shown');
|
||||
await click(`${SELECTORS.deleteRoleButton} [data-test-confirm-action-trigger]`);
|
||||
await click(`[data-test-confirm-button]`);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${this.mountPath}/pki/roles`,
|
||||
'redirects to roles list after deletion'
|
||||
);
|
||||
});
|
||||
|
||||
test('it does not show toolbar items the user does not have permission to see', async function (assert) {
|
||||
await authPage.login(this.pkiRoleReader);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.dom(SELECTORS.rolesTab).exists('Roles tab is present');
|
||||
await click(SELECTORS.rolesTab);
|
||||
assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list');
|
||||
await click('.linked-block');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
assert.dom(SELECTORS.deleteRoleButton).doesNotExist('Delete role button is not shown');
|
||||
assert.dom(SELECTORS.generateCertLink).doesNotExist('Generate cert link is not shown');
|
||||
assert.dom(SELECTORS.signCertLink).doesNotExist('Sign cert link is not shown');
|
||||
assert.dom(SELECTORS.editRoleLink).doesNotExist('Edit link is not shown');
|
||||
});
|
||||
|
||||
test('it shows correct toolbar items for the user policy', async function (assert) {
|
||||
await authPage.login(this.pkiRoleEditor);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.dom(SELECTORS.rolesTab).exists('Roles tab is present');
|
||||
await click(SELECTORS.rolesTab);
|
||||
assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list');
|
||||
await click('.linked-block');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
|
||||
assert.dom(SELECTORS.deleteRoleButton).doesNotExist('Delete role button is not shown');
|
||||
assert.dom(SELECTORS.generateCertLink).doesNotExist('Generate cert link is not shown');
|
||||
assert.dom(SELECTORS.signCertLink).doesNotExist('Sign cert link is not shown');
|
||||
assert.dom(SELECTORS.editRoleLink).exists('Edit link is shown');
|
||||
await click(SELECTORS.editRoleLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/edit`);
|
||||
});
|
||||
|
||||
test('create role happy path', async function (assert) {
|
||||
const roleName = 'another-role';
|
||||
await authPage.login(this.pkiAdminToken);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert.dom(SELECTORS.rolesTab).exists('Roles tab is present');
|
||||
await click(SELECTORS.rolesTab);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
await click(SELECTORS.createRoleLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/create`);
|
||||
assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumbs are rendered');
|
||||
assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs');
|
||||
assert.dom(SELECTORS.pageTitle).hasText('Create a PKI role');
|
||||
|
||||
await fillIn(SELECTORS.roleForm.roleName, roleName);
|
||||
await click(SELECTORS.roleForm.roleCreateButton);
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/${roleName}/details`);
|
||||
assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs');
|
||||
assert.dom(SELECTORS.pageTitle).hasText(`PKI Role ${roleName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,4 @@
|
||||
export const SELECTORS = {
|
||||
breadcrumbContainer: '[data-test-breadcrumbs]',
|
||||
breadcrumbs: '[data-test-breadcrumbs] 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"]',
|
||||
|
||||
22
ui/tests/helpers/pki/workflow.js
Normal file
22
ui/tests/helpers/pki/workflow.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { SELECTORS as ROLEFORM } from './roles/form';
|
||||
export const SELECTORS = {
|
||||
breadcrumbContainer: '[data-test-breadcrumbs]',
|
||||
breadcrumbs: '[data-test-breadcrumbs] li',
|
||||
pageTitle: '[data-test-pki-role-page-title]',
|
||||
// TABS
|
||||
overviewTab: '[data-test-secret-list-tab="Overview"]',
|
||||
rolesTab: '[data-test-secret-list-tab="Roles"]',
|
||||
issuersTab: '[data-test-secret-list-tab="Issuers"]',
|
||||
certsTab: '[data-test-secret-list-tab="Certificates"]',
|
||||
keysTab: '[data-test-secret-list-tab="Keys"]',
|
||||
configTab: '[data-test-secret-list-tab="Configuration"]',
|
||||
// ROLES
|
||||
deleteRoleButton: '[data-test-pki-role-delete]',
|
||||
generateCertLink: '[data-test-pki-role-generate-cert]',
|
||||
signCertLink: '[data-test-pki-role-sign-cert]',
|
||||
editRoleLink: '[data-test-pki-role-edit-link]',
|
||||
createRoleLink: '[data-test-pki-role-create-link]',
|
||||
roleForm: {
|
||||
...ROLEFORM,
|
||||
},
|
||||
};
|
||||
@@ -27,7 +27,7 @@ module('Integration | Component | pki key details page', function (hooks) {
|
||||
});
|
||||
|
||||
test('it renders the page component and deletes a key', async function (assert) {
|
||||
assert.expect(9);
|
||||
assert.expect(7);
|
||||
this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => {
|
||||
assert.ok(true, 'confirming delete fires off destroyRecord()');
|
||||
});
|
||||
@@ -39,8 +39,6 @@ module('Integration | Component | pki key details page', function (hooks) {
|
||||
{ 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('View key', 'title renders');
|
||||
assert.dom(SELECTORS.keyIdValue).hasText(' 724862ff-6438-bad0-b598-77a6c7f4e934', 'key id renders');
|
||||
assert.dom(SELECTORS.keyNameValue).hasText('test-key', 'key name renders');
|
||||
|
||||
@@ -11,9 +11,8 @@ module('Integration | Component | pki-key-parameters', function (hooks) {
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.model = this.store.createRecord('pki/role');
|
||||
this.model.backend = 'pki';
|
||||
[this.fields] = Object.values(this.model.fieldGroups.find((g) => g['Key parameters']));
|
||||
this.model = this.store.createRecord('pki/role', { backend: 'pki' });
|
||||
[this.fields] = Object.values(this.model.formFieldGroups.find((g) => g['Key parameters']));
|
||||
});
|
||||
|
||||
test('it should render the component and display the correct defaults', async function (assert) {
|
||||
|
||||
@@ -22,17 +22,13 @@ module('Integration | Component | pki role details page', function (hooks) {
|
||||
});
|
||||
|
||||
test('it should render the page component', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(5);
|
||||
await render(
|
||||
hbs`
|
||||
<Page::PkiRoleDetails @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
|
||||
|
||||
Reference in New Issue
Block a user