UI: PKI Generate Root Form (#18712)
This commit is contained in:
parent
be5b38e53d
commit
81d36b61f1
|
@ -0,0 +1,41 @@
|
|||
import { assert } from '@ember/debug';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
import ApplicationAdapter from '../application';
|
||||
|
||||
export default class PkiActionAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
urlForCreateRecord(modelName, snapshot) {
|
||||
const { backend, type } = snapshot.record;
|
||||
const { actionType, useIssuer } = snapshot.adapterOptions;
|
||||
if (!backend || !actionType) {
|
||||
throw new Error('URL for create record is missing required attributes');
|
||||
}
|
||||
const baseUrl = `${this.buildURL()}/${encodePath(backend)}`;
|
||||
switch (actionType) {
|
||||
case 'import':
|
||||
return useIssuer ? `${baseUrl}/issuers/import/bundle` : `${baseUrl}/config/ca`;
|
||||
case 'generate-root':
|
||||
return useIssuer ? `${baseUrl}/issuers/generate/root/${type}` : `${baseUrl}/root/generate/${type}`;
|
||||
case 'generate-csr':
|
||||
return useIssuer
|
||||
? `${baseUrl}/issuers/generate/intermediate/${type}`
|
||||
: `${baseUrl}/intermediate/generate/${type}`;
|
||||
default:
|
||||
assert('actionType must be one of import, generate-root, or generate-csr');
|
||||
}
|
||||
}
|
||||
|
||||
createRecord(store, type, snapshot) {
|
||||
const serializer = store.serializerFor(type.modelName);
|
||||
const url = this.urlForCreateRecord(type.modelName, snapshot);
|
||||
// Send actionType as serializer requestType so that we serialize data based on the endpoint
|
||||
const data = serializer.serialize(snapshot, snapshot.adapterOptions.actionType);
|
||||
return this.ajax(url, 'POST', { data }).then((result) => ({
|
||||
// pki/action endpoints don't correspond with a single specific entity,
|
||||
// so in ember-data we'll map it to the request ID
|
||||
id: result.request_id,
|
||||
...result,
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { assert } from '@ember/debug';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
import ApplicationAdapter from '../application';
|
||||
|
||||
export default class PkiConfigAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
urlForCreateRecord(modelName, snapshot) {
|
||||
const { backend, type } = snapshot.record;
|
||||
const { formType, useIssuer } = snapshot.adapterOptions;
|
||||
if (!backend || !formType) {
|
||||
throw new Error('URL for create record is missing required attributes');
|
||||
}
|
||||
const baseUrl = `${this.buildURL()}/${encodePath(backend)}`;
|
||||
switch (formType) {
|
||||
case 'import':
|
||||
return useIssuer ? `${baseUrl}/issuers/import/bundle` : `${baseUrl}/config/ca`;
|
||||
case 'generate-root':
|
||||
return useIssuer ? `${baseUrl}/issuers/generate/root/${type}` : `${baseUrl}/root/generate/${type}`;
|
||||
case 'generate-csr':
|
||||
return useIssuer
|
||||
? `${baseUrl}/issuers/generate/intermediate/${type}`
|
||||
: `${baseUrl}/intermediate/generate/${type}`;
|
||||
default:
|
||||
assert('formType must be one of import, generate-root, or generate-csr');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,17 +22,17 @@ export function withFormFields(propertyNames, groupPropertyNames) {
|
|||
return class ModelFormFields extends SuperClass {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
if (!Array.isArray(propertyNames) && !Array.isArray(groupPropertyNames)) {
|
||||
throw new Error(
|
||||
'Array of property names and/or array of field groups are required when using withFormFields model decorator'
|
||||
);
|
||||
}
|
||||
if (propertyNames) {
|
||||
this.formFields = expandAttributeMeta(this, propertyNames);
|
||||
}
|
||||
if (groupPropertyNames) {
|
||||
this.formFieldGroups = fieldToAttrs(this, groupPropertyNames);
|
||||
}
|
||||
const allFields = [];
|
||||
this.eachAttribute(function (key) {
|
||||
allFields.push(key);
|
||||
});
|
||||
this.allFields = expandAttributeMeta(this, allFields);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
import { inject as service } from '@ember/service';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { withFormFields } from 'vault/decorators/model-form-fields';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
|
||||
const validations = {
|
||||
type: [{ type: 'presence', message: 'Type is required.' }],
|
||||
commonName: [{ type: 'presence', message: 'Common name is required.' }],
|
||||
issuerName: [
|
||||
{
|
||||
validator(model) {
|
||||
if (model.issuerName === 'default') return false;
|
||||
return true;
|
||||
},
|
||||
message: 'Issuer name must be unique across all issuers and not be the reserved value default.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@withModelValidations(validations)
|
||||
@withFormFields()
|
||||
export default class PkiActionModel extends Model {
|
||||
@service secretMountPath;
|
||||
|
||||
/* actionType import */
|
||||
@attr('string') pemBundle;
|
||||
|
||||
/* actionType generate-root */
|
||||
@attr('string', {
|
||||
possibleValues: ['exported', 'internal', 'existing', 'kms'],
|
||||
noDefault: true,
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('string') issuerName; // REQUIRED, cannot be "default"
|
||||
|
||||
@attr('string') keyName; // cannot be "default"
|
||||
|
||||
@attr('string', {
|
||||
defaultValue: 'default',
|
||||
label: 'Key reference',
|
||||
})
|
||||
keyRef; // type=existing only
|
||||
|
||||
@attr('string') commonName; // REQUIRED
|
||||
|
||||
@attr('string', {
|
||||
label: 'Subject Alternative Names (SANs)',
|
||||
})
|
||||
altNames; // comma sep strings
|
||||
|
||||
@attr('string', {
|
||||
label: 'IP Subject Alternative Names (IP SANs)',
|
||||
})
|
||||
ipSans;
|
||||
|
||||
@attr('string', {
|
||||
label: 'URI Subject Alternative Names (URI SANs)',
|
||||
})
|
||||
uriSans;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Other SANs',
|
||||
})
|
||||
otherSans;
|
||||
|
||||
@attr('string', {
|
||||
defaultValue: 'pem',
|
||||
possibleValues: ['pem', 'der', 'pem_bundle'],
|
||||
})
|
||||
format;
|
||||
|
||||
@attr('string', {
|
||||
defaultValue: 'der',
|
||||
possibleValues: ['der', 'pkcs8'],
|
||||
})
|
||||
privateKeyFormat;
|
||||
|
||||
@attr('string', {
|
||||
defaultValue: 'rsa',
|
||||
possibleValues: ['rsa', 'ed25519', 'ec'],
|
||||
})
|
||||
keyType;
|
||||
|
||||
@attr('string', {
|
||||
defaultValue: '0',
|
||||
// options management happens in pki-key-parameters
|
||||
})
|
||||
keyBits;
|
||||
|
||||
@attr('number', {
|
||||
defaultValue: -1,
|
||||
})
|
||||
maxPathLength;
|
||||
|
||||
@attr('boolean', {
|
||||
label: 'Exclude common name from SANs',
|
||||
subText:
|
||||
'If checked, the common name will not be included in DNS or Email Subject Alternate Names. This is useful if the CN is a human-readable identifier, not a hostname or email address.',
|
||||
defaultValue: false,
|
||||
})
|
||||
excludeCnFromSans;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Permitted DNS domains',
|
||||
})
|
||||
permittedDnsDomains;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Organizational Units (OU)',
|
||||
})
|
||||
ou;
|
||||
@attr('string') organization;
|
||||
@attr('string') country;
|
||||
@attr('string') locality;
|
||||
@attr('string') province;
|
||||
@attr('string') streetAddress;
|
||||
@attr('string') postalCode;
|
||||
|
||||
@attr('string', {
|
||||
subText: "Specifies the requested Subject's named Serial Number value.",
|
||||
})
|
||||
serialNumber;
|
||||
|
||||
@attr({
|
||||
label: 'Backdate validity',
|
||||
detailsLabel: 'Issued certificate backdating',
|
||||
helperTextDisabled: 'Vault will use the default value, 30s',
|
||||
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',
|
||||
defaultValue: '30s',
|
||||
})
|
||||
notBeforeDuration;
|
||||
|
||||
@attr('string') managedKeyName;
|
||||
@attr('string', {
|
||||
label: 'Managed key UUID',
|
||||
})
|
||||
managedKeyId;
|
||||
|
||||
@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.',
|
||||
editType: 'yield',
|
||||
})
|
||||
customTtl;
|
||||
@attr('string') ttl;
|
||||
@attr('date') notAfter;
|
||||
|
||||
get backend() {
|
||||
return this.secretMountPath.currentPath;
|
||||
}
|
||||
|
||||
// To determine which endpoint the config adapter should use,
|
||||
// we want to check capabilities on the newer endpoints (those
|
||||
// prefixed with "issuers") and use the old path as fallback
|
||||
// if user does not have permissions.
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/import/bundle`, 'backend') importBundlePath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/generate/root/${'type'}`, 'backend', 'type')
|
||||
generateIssuerRootPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/generate/intermediate/${'type'}`, 'backend', 'type')
|
||||
generateIssuerCsrPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/cross-sign`, 'backend') crossSignPath;
|
||||
|
||||
get canImportBundle() {
|
||||
return this.importBundlePath.get('canCreate') === true;
|
||||
}
|
||||
get canGenerateIssuerRoot() {
|
||||
return this.generateIssuerRootPath.get('canCreate') === true;
|
||||
}
|
||||
get canGenerateIssuerIntermediate() {
|
||||
return this.generateIssuerCsrPath.get('canCreate') === true;
|
||||
}
|
||||
get canCrossSign() {
|
||||
return this.crossSignPath.get('canCreate') === true;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
import { inject as service } from '@ember/service';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
|
||||
export default class PkiConfigModel extends Model {
|
||||
@service secretMountPath;
|
||||
|
||||
@attr('string') pemBundle;
|
||||
@attr('string') type;
|
||||
|
||||
get backend() {
|
||||
return this.secretMountPath.currentPath;
|
||||
}
|
||||
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/import/bundle`, 'backend') importBundlePath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/generate/root/${'type'}`, 'backend', 'type')
|
||||
generateIssuerRootPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuers/generate/intermediate/${'type'}`, 'backend', 'type')
|
||||
generateIssuerCsrPath;
|
||||
|
||||
get canImportBundle() {
|
||||
return this.importBundlePath.get('canCreate') !== false;
|
||||
}
|
||||
get canGenerateIssuerRoot() {
|
||||
return this.generateIssuerRootPath.get('canCreate') !== false;
|
||||
}
|
||||
get canGenerateIssuerIntermediate() {
|
||||
return this.generateIssuerCsrPath.get('canCreate') !== false;
|
||||
}
|
||||
get canCrossSign() {
|
||||
return this.crossSignPath.get('canCreate') !== false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import ApplicationSerializer from '../application';
|
||||
|
||||
export default class PkiActionSerializer extends ApplicationSerializer {
|
||||
attrs = {
|
||||
customTtl: { serialize: false },
|
||||
type: { serialize: false },
|
||||
};
|
||||
|
||||
serialize(snapshot, requestType) {
|
||||
const data = super.serialize(snapshot);
|
||||
// requestType is a custom value specified from the pki/action adapter
|
||||
const allowedPayloadAttributes = this._allowedParamsByType(requestType);
|
||||
if (!allowedPayloadAttributes) return data;
|
||||
|
||||
const payload = {};
|
||||
allowedPayloadAttributes.forEach((key) => {
|
||||
if ('undefined' !== typeof data[key]) {
|
||||
payload[key] = data[key];
|
||||
}
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
|
||||
_allowedParamsByType(actionType) {
|
||||
switch (actionType) {
|
||||
case 'import':
|
||||
return ['pem_bundle'];
|
||||
case 'generate-root':
|
||||
return [
|
||||
'alt_names',
|
||||
'common_name',
|
||||
'country',
|
||||
'exclude_cn_from_sans',
|
||||
'format',
|
||||
'ip_sans',
|
||||
'issuer_name',
|
||||
'key_bits',
|
||||
'key_name',
|
||||
'key_ref',
|
||||
'key_type',
|
||||
'locality',
|
||||
'managed_key_id',
|
||||
'managed_key_name',
|
||||
'max_path_length',
|
||||
'not_after',
|
||||
'not_before_duration',
|
||||
'organization',
|
||||
'other_sans',
|
||||
'ou',
|
||||
'permitted_dns_domains',
|
||||
'postal_code',
|
||||
'private_key_format',
|
||||
'province',
|
||||
'serial_number',
|
||||
'street_address',
|
||||
'type',
|
||||
];
|
||||
default:
|
||||
// if type doesn't match, serialize all
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import ApplicationSerializer from '../application';
|
||||
|
||||
export default class PkiConfigSerializer extends ApplicationSerializer {}
|
|
@ -0,0 +1,33 @@
|
|||
# Vault PKI
|
||||
|
||||
Welcome to the Vault PKI (Ember) Engine! Below is an overview of PKI and resources for how to get started working within this engine.
|
||||
|
||||
## About PKI
|
||||
|
||||
> Public Key Infrastructure (PKI) is a system of processes, technologies, and policies that allows you to encrypt and sign data. (source: [digicert.com](https://www.digicert.com/what-is-pki))
|
||||
|
||||
The [Vault PKI Secrets Engine](https://developer.hashicorp.com/vault/api-docs/secret/pki) allows security engineers to [create a chain of PKI certificates](https://developer.hashicorp.com/vault/tutorials/secrets-management/pki-engine) much easier than they would with traditional workflows.
|
||||
|
||||
## About the UI engine
|
||||
|
||||
If you couldn't tell from the documentation above, PKI is _complex_. As such, the data doesn't map cleanly to a CRUD model and so the first thing you might notice is that the models and adapters for PKI (which [live in the main app](https://ember-engines.com/docs/addons#using-ember-data), not the engine) have some custom logic that differentiate it from most other secret engines. Below are the model
|
||||
|
||||
### pki/key
|
||||
|
||||
TBD
|
||||
|
||||
### pki/role
|
||||
|
||||
TBD
|
||||
|
||||
### pki/issuer
|
||||
|
||||
TBD
|
||||
|
||||
### pki/certificate/\*
|
||||
|
||||
TBD
|
||||
|
||||
### pki/action
|
||||
|
||||
TBD
|
|
@ -7,7 +7,7 @@ import { tracked } from '@glimmer/tracking';
|
|||
import { waitFor } from '@ember/test-waiters';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import PkiBaseCertificateModel from 'vault/models/pki/certificate/base';
|
||||
import PkiConfigModel from 'vault/models/pki/config';
|
||||
import PkiActionModel from 'vault/models/pki/action';
|
||||
|
||||
/**
|
||||
* @module PkiCaCertificateImport
|
||||
|
@ -27,7 +27,7 @@ import PkiConfigModel from 'vault/models/pki/config';
|
|||
interface Args {
|
||||
onSave: CallableFunction;
|
||||
onCancel: CallableFunction;
|
||||
model: PkiBaseCertificateModel | PkiConfigModel;
|
||||
model: PkiBaseCertificateModel | PkiActionModel;
|
||||
adapterOptions: object | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="columns">
|
||||
{{#each this.configTypes as |option|}}
|
||||
<div class="column is-flex">
|
||||
<label for={{option.key}} class="box-label is-column {{if (eq this.formType option.key) 'is-selected'}}">
|
||||
<label for={{option.key}} class="box-label is-column {{if (eq this.actionType option.key) 'is-selected'}}">
|
||||
<div>
|
||||
<h3 class="box-label-header title is-6">
|
||||
<Icon @size="24" @name={{option.icon}} />
|
||||
|
@ -17,8 +17,8 @@
|
|||
id={{option.key}}
|
||||
name="pki-config-type"
|
||||
@value={{option.key}}
|
||||
@groupValue={{this.formType}}
|
||||
@onChange={{fn (mut this.formType) option.key}}
|
||||
@groupValue={{this.actionType}}
|
||||
@onChange={{fn (mut this.actionType) option.key}}
|
||||
data-test-pki-config-option={{option.key}}
|
||||
/>
|
||||
<label for={{option.key}}></label>
|
||||
|
@ -27,16 +27,21 @@
|
|||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if (eq this.formType "import")}}
|
||||
{{#if (eq this.actionType "import")}}
|
||||
<PkiCaCertificateImport
|
||||
@model={{@config}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
@adapterOptions={{hash formType=this.formType useIssuer=this.shouldUseIssuerEndpoint}}
|
||||
@adapterOptions={{hash actionType=this.actionType useIssuer=@config.canImportBundle}}
|
||||
/>
|
||||
{{else if (eq this.formType "generate-root")}}
|
||||
<code>POST /root/generate/:type ~or~ /issuers/generate/root/:type</code>
|
||||
{{else if (eq this.formType "generate-csr")}}
|
||||
{{else if (eq this.actionType "generate-root")}}
|
||||
<PkiGenerateRoot
|
||||
@model={{@config}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers"}}
|
||||
@onCancel={{this.cancel}}
|
||||
@adapterOptions={{hash actionType="generate-root" useIssuer=@config.canGenerateIssuerRoot}}
|
||||
/>
|
||||
{{else if (eq this.actionType "generate-csr")}}
|
||||
<code>POST /intermediate/generate/:type ~or~ /issuers/generate/intermediate/:type</code>
|
||||
{{else}}
|
||||
<EmptyState @title="Choose an option" @message="To see configuration options, choose your desired output above." />
|
||||
|
@ -45,9 +50,9 @@
|
|||
<button type="button" class="button is-primary" disabled={{true}} data-test-pki-config-save>
|
||||
Done
|
||||
</button>
|
||||
<LinkTo @route="overview" class="button has-left-margin-s" data-test-pki-config-cancel>
|
||||
<button type="button" class="button has-left-margin-s" {{on "click" this.cancel}} data-test-pki-config-cancel>
|
||||
Cancel
|
||||
</LinkTo>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -4,11 +4,12 @@ import { inject as service } from '@ember/service';
|
|||
import Store from '@ember-data/store';
|
||||
import Router from '@ember/routing/router';
|
||||
import FlashMessageService from 'vault/services/flash-messages';
|
||||
import PkiConfigModel from 'vault/models/pki/config';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import PkiActionModel from 'vault/models/pki/action';
|
||||
|
||||
interface Args {
|
||||
config: PkiConfigModel;
|
||||
config: PkiActionModel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,7 +23,7 @@ export default class PkiConfigureForm extends Component<Args> {
|
|||
@service declare readonly store: Store;
|
||||
@service declare readonly router: Router;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@tracked formType = '';
|
||||
@tracked actionType = '';
|
||||
|
||||
get configTypes() {
|
||||
return [
|
||||
|
@ -56,7 +57,7 @@ export default class PkiConfigureForm extends Component<Args> {
|
|||
// we want to check capabilities on the newer endpoints (those
|
||||
// prefixed with "issuers") and use the old path as fallback
|
||||
// if user does not have permissions.
|
||||
switch (this.formType) {
|
||||
switch (this.actionType) {
|
||||
case 'import':
|
||||
return config.canImportBundle;
|
||||
case 'generate-root':
|
||||
|
@ -67,4 +68,9 @@ export default class PkiConfigureForm extends Component<Args> {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@action cancel() {
|
||||
this.args.config.rollbackAttributes();
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.overview');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<form {{on "submit" this.generateRoot}} data-test-pki-config-generate-root-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-generate-root-title="Root parameters">
|
||||
Root parameters
|
||||
</h2>
|
||||
{{#each this.defaultFields as |field|}}
|
||||
{{#let (find-by "name" field @model.allFields) as |attr|}}
|
||||
<FormField
|
||||
@attr={{attr}}
|
||||
@model={{@model}}
|
||||
@modelValidations={{this.modelValidations}}
|
||||
@onChange={{this.checkFormValidity}}
|
||||
data-test-field
|
||||
>
|
||||
{{#if (eq field "customTtl")}}
|
||||
{{! customTtl attr has editType yield, which will render this }}
|
||||
<PkiNotValidAfterForm @attr={{attr}} @model={{@model}} />
|
||||
{{/if}}
|
||||
</FormField>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
|
||||
{{! togglable groups }}
|
||||
{{#each-in this.groups as |group fields|}}
|
||||
<ToggleButton
|
||||
@isOpen={{eq this.showGroup group}}
|
||||
@openLabel={{concat "Hide " group}}
|
||||
@closedLabel={{group}}
|
||||
@onClick={{fn this.toggleGroup group}}
|
||||
class="is-block"
|
||||
data-test-toggle-group={{group}}
|
||||
/>
|
||||
{{#if (eq this.showGroup group)}}
|
||||
<div class="box is-marginless" data-test-group={{group}}>
|
||||
{{#if (eq group "Key parameters")}}
|
||||
<p class="has-bottom-margin-m" data-test-toggle-group-description>
|
||||
{{#if (eq @model.type "internal")}}
|
||||
This certificate type is internal. This means that the private key will not be returned and cannot be retrieved
|
||||
later. Below, you will name the key and define its type and key bits.
|
||||
{{else if (eq @model.type "kms")}}
|
||||
This certificate type is kms, meaning managed keys will be used. Below, you will name the key and tell Vault
|
||||
where to find it in your KMS or HSM.
|
||||
<DocLink @path="/vault/docs/enterprise/managed-keys">Learn more about managed keys.</DocLink>
|
||||
{{else if (eq @model.type "exported")}}
|
||||
This certificate type is exported. This means the private key will be returned in the response. Below, you will
|
||||
name the key and define its type and key bits.
|
||||
{{else if (eq @model.type "existing")}}
|
||||
You chose to use an existing key. This means that we’ll use the key reference to create the CSR or root. Please
|
||||
provide the reference to the key.
|
||||
{{else}}
|
||||
Please choose a type to see key parameter options.
|
||||
{{/if}}
|
||||
</p>
|
||||
{{#if this.keyParamFields}}
|
||||
<PkiKeyParameters @model={{@model}} @fields={{this.keyParamFields}} />
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<p class="has-bottom-margin-m" data-test-toggle-group-description>
|
||||
{{#if (eq group "Subject Alternative Name (SAN) Options")}}
|
||||
SAN fields are an extension that allow you specify additional host names (sites, IP addresses, common names,
|
||||
etc.) to be protected by a single certificate.
|
||||
{{else if (eq group "Additional subject fields")}}
|
||||
These fields provide more information about the client to which the certificate belongs.
|
||||
{{/if}}
|
||||
</p>
|
||||
{{#each fields as |fieldName|}}
|
||||
{{#let (find-by "name" fieldName @model.allFields) as |attr|}}
|
||||
<FormField data-test-field @attr={{attr}} @mode="create" @model={{@model}} />
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each-in}}
|
||||
|
||||
{{!-- TODO: this section
|
||||
<fieldset class="box is-shadowless is-marginless is-borderless is-fullwidth">
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-generate-root-title="Issuer URLs">Issuer URLs</h2>
|
||||
{{! Updating this area of the form will require a secondary call to issuer/:issuer_ref/update once the initial request is complete }}
|
||||
</fieldset> --}}
|
||||
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary" data-test-pki-generate-root-save>
|
||||
Done
|
||||
</button>
|
||||
<button {{on "click" this.cancel}} type="button" class="button has-left-margin-s" data-test-pki-generate-root-cancel>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<div class="control">
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
@paddingTop={{true}}
|
||||
@message={{this.invalidFormAlert}}
|
||||
@mimicRefresh={{true}}
|
||||
data-test-pki-generate-root-validation-error
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,117 @@
|
|||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
/**
|
||||
* @module PkiGenerateRoot
|
||||
* PkiGenerateRoot shows only the fields valid for the generate root endpoint.
|
||||
* This form handles the model save and rollback actions, and will call the passed
|
||||
* onSave and onCancel args for transition (passed from parent).
|
||||
* NOTE: this component is not TS because decorator-added parameters (eg validator and
|
||||
* formFields) aren't recognized on the model.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <PkiGenerateRoot @model={{this.model}} @onCancel={{transition-to "vault.cluster"}} @onSave={{transition-to "vault.cluster.secrets"}} @adapterOptions={{hash actionType="import" useIssuer=false}} />
|
||||
* ```
|
||||
*
|
||||
* @param {Object} model - pki/action model.
|
||||
* @callback onCancel - Callback triggered when cancel button is clicked, after model is unloaded
|
||||
* @callback onSave - Callback triggered after model save success.
|
||||
* @param {Object} adapterOptions - object passed as adapterOptions on the model.save method
|
||||
*/
|
||||
export default class PkiGenerateRootComponent extends Component {
|
||||
@service flashMessages;
|
||||
@tracked showGroup = null;
|
||||
@tracked modelValidations = null;
|
||||
@tracked errorBanner = '';
|
||||
@tracked invalidFormAlert = '';
|
||||
|
||||
@action
|
||||
toggleGroup(group, isOpen) {
|
||||
this.showGroup = isOpen ? group : null;
|
||||
}
|
||||
|
||||
get defaultFields() {
|
||||
return [
|
||||
'type',
|
||||
'commonName',
|
||||
'issuerName',
|
||||
'customTtl',
|
||||
'notBeforeDuration',
|
||||
'format',
|
||||
'permittedDnsDomains',
|
||||
'maxPathLength',
|
||||
];
|
||||
}
|
||||
get keyParamFields() {
|
||||
const { type } = this.args.model;
|
||||
if (!type) return null;
|
||||
let fields = ['keyName', 'keyType', 'keyBits'];
|
||||
if (type === 'existing') {
|
||||
fields = ['keyRef'];
|
||||
} else if (type === 'kms') {
|
||||
fields = ['keyName', 'managedKeyName', 'managedKeyId'];
|
||||
}
|
||||
return fields.map((fieldName) => {
|
||||
return this.args.model.allFields.find((attr) => attr.name === fieldName);
|
||||
});
|
||||
}
|
||||
|
||||
@action cancel() {
|
||||
// Generate root form will always have a new model
|
||||
this.args.model.unloadRecord();
|
||||
this.args.onCancel();
|
||||
}
|
||||
|
||||
get groups() {
|
||||
return {
|
||||
'Key parameters': this.keyParamFields,
|
||||
'Subject Alternative Name (SAN) Options': [
|
||||
'excludeCnFromSans',
|
||||
'serialNumber',
|
||||
'altNames',
|
||||
'ipSans',
|
||||
'uriSans',
|
||||
'otherSans',
|
||||
],
|
||||
'Additional subject fields': [
|
||||
'ou',
|
||||
'organization',
|
||||
'country',
|
||||
'locality',
|
||||
'province',
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
checkFormValidity() {
|
||||
if (this.args.model.validate) {
|
||||
const { isValid, state, invalidFormMessage } = this.args.model.validate();
|
||||
this.modelValidations = state;
|
||||
this.invalidFormAlert = invalidFormMessage;
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@action
|
||||
async generateRoot(event) {
|
||||
event.preventDefault();
|
||||
const continueSave = this.checkFormValidity();
|
||||
if (!continueSave) return;
|
||||
try {
|
||||
await this.args.model.save({ adapterOptions: this.args.adapterOptions });
|
||||
this.flashMessages.success('Successfully generated root.');
|
||||
this.args.onSave();
|
||||
} catch (e) {
|
||||
this.errorBanner = errorMessage(e);
|
||||
this.invalidFormAlert = 'There was a problem generating the root.';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
{{yield}}
|
||||
{{#each @fields as |attr|}}
|
||||
{{#if (eq attr.name "keyBits")}}
|
||||
<div class="field">
|
||||
<FormFieldLabel for={{attr.name}} @label={{attr.options.label}} @subText={{attr.options.subText}} />
|
||||
<div class="field" data-test-field="keyBits">
|
||||
<FormFieldLabel
|
||||
for={{attr.name}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@subText={{attr.options.subText}}
|
||||
/>
|
||||
<div class="control is-expanded">
|
||||
<ToolTip @verticalPosition="above" @horizontalPosition="center" as |T|>
|
||||
<T.Trigger tabindex="-1">
|
||||
|
|
|
@ -15,8 +15,8 @@ import { action } from '@ember/object';
|
|||
|
||||
// first value in array is the default bits for that key type
|
||||
const KEY_BITS_OPTIONS = {
|
||||
rsa: ['2048', '3072', '4096'],
|
||||
ec: ['256', '224', '384', '521'],
|
||||
rsa: ['2048', '3072', '4096', '0'],
|
||||
ec: ['256', '224', '384', '521', '0'],
|
||||
ed25519: ['0'],
|
||||
any: ['0'],
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ export default class PkiConfigurationCreateRoute extends Route {
|
|||
@service store;
|
||||
|
||||
model() {
|
||||
return this.store.createRecord('pki/config', {});
|
||||
return this.store.createRecord('pki/action', {});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class PkiIssuersGenerateRootRoute extends Route {}
|
||||
export default class PkiIssuersGenerateRootRoute extends Route {
|
||||
@service secretMountPath;
|
||||
@service store;
|
||||
|
||||
model() {
|
||||
return this.store.createRecord('pki/action');
|
||||
}
|
||||
|
||||
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: 'generate root' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,17 @@
|
|||
route: issuers.generate-root
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-issuers-page-title>
|
||||
Generate root
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiGenerateRoot
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@adapterOptions={{hash actionType="generate-root" useIssuer=this.model.canGenerateIssuerRoot}}
|
||||
/>
|
|
@ -0,0 +1,191 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { click, fillIn, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import Sinon from 'sinon';
|
||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
|
||||
const SELECTORS = {
|
||||
mainSectionTitle: '[data-test-generate-root-title="Root parameters"]',
|
||||
urlSectionTitle: '[data-test-generate-root-title="Issuer URLs"]',
|
||||
keyParamsGroupToggle: '[data-test-toggle-group="Key parameters"]',
|
||||
sanGroupToggle: '[data-test-toggle-group="Subject Alternative Name (SAN) Options"]',
|
||||
additionalGroupToggle: '[data-test-toggle-group="Additional subject fields"]',
|
||||
toggleGroupDescription: '[data-test-toggle-group-description]',
|
||||
formField: '[data-test-field]',
|
||||
typeField: '[data-test-input="type"]',
|
||||
fieldByName: (name) => `[data-test-field="${name}"]`,
|
||||
saveButton: '[data-test-pki-generate-root-save]',
|
||||
cancelButton: '[data-test-pki-generate-root-cancel]',
|
||||
formInvalidError: '[data-test-pki-generate-root-validation-error]',
|
||||
};
|
||||
|
||||
module('Integration | Component | pki-generate-root', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupEngine(hooks, 'pki');
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||
this.secretMountPath.currentPath = 'pki-test';
|
||||
this.model = this.store.createRecord('pki/action', {
|
||||
role: 'my-role',
|
||||
});
|
||||
this.onSave = Sinon.spy();
|
||||
this.onCancel = Sinon.spy();
|
||||
});
|
||||
|
||||
test('it renders with correct sections', async function (assert) {
|
||||
await render(
|
||||
hbs`<PkiGenerateRoot @model={{this.model}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
|
||||
{
|
||||
owner: this.engine,
|
||||
}
|
||||
);
|
||||
|
||||
// Titles
|
||||
assert.dom(SELECTORS.mainSectionTitle).hasText('Root parameters');
|
||||
// TODO: Add this back once URLs section is added
|
||||
// assert.dom('h2').exists({ count: 2 }, 'two H2 titles are visible on page load');
|
||||
// assert.dom(SELECTORS.urlSectionTitle).hasText('Issuer URLs');
|
||||
|
||||
assert.dom('[data-test-toggle-group]').exists({ count: 3 }, '3 toggle groups shown');
|
||||
});
|
||||
|
||||
test('it shows the appropriate fields under the toggles', async function (assert) {
|
||||
await render(
|
||||
hbs`<PkiGenerateRoot @model={{this.model}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
|
||||
{
|
||||
owner: this.engine,
|
||||
}
|
||||
);
|
||||
|
||||
await click(SELECTORS.additionalGroupToggle);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText('These fields provide more information about the client to which the certificate belongs.');
|
||||
assert
|
||||
.dom(`[data-test-group="Additional subject fields"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 7 }, '7 form fields under Additional Fields toggle');
|
||||
|
||||
await click(SELECTORS.sanGroupToggle);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'SAN fields are an extension that allow you specify additional host names (sites, IP addresses, common names, etc.) to be protected by a single certificate.'
|
||||
);
|
||||
assert
|
||||
.dom(`[data-test-group="Subject Alternative Name (SAN) Options"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 6 }, '7 form fields under SANs toggle');
|
||||
|
||||
await click(SELECTORS.keyParamsGroupToggle);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'Please choose a type to see key parameter options.',
|
||||
'Shows empty state description before type is selected'
|
||||
);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 0 }, '0 form fields under keyParams toggle');
|
||||
});
|
||||
|
||||
test('it renders the correct form fields in key params', async function (assert) {
|
||||
this.set('type', '');
|
||||
await render(
|
||||
hbs`<PkiGenerateRoot @model={{this.model}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
|
||||
{
|
||||
owner: this.engine,
|
||||
}
|
||||
);
|
||||
await click(SELECTORS.keyParamsGroupToggle);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 0 }, '0 form fields under keyParams toggle');
|
||||
|
||||
this.set('type', 'exported');
|
||||
await fillIn(SELECTORS.typeField, this.type);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'This certificate type is exported. This means the private key will be returned in the response. Below, you will name the key and define its type and key bits.',
|
||||
`has correct description for type=${this.type}`
|
||||
);
|
||||
assert.strictEqual(this.model.type, this.type);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 3 }, '3 form fields under keyParams toggle');
|
||||
assert.dom(SELECTORS.fieldByName('keyName')).exists(`Key name field shown when type=${this.type}`);
|
||||
assert.dom(SELECTORS.fieldByName('keyType')).exists(`Key type field shown when type=${this.type}`);
|
||||
assert.dom(SELECTORS.fieldByName('keyBits')).exists(`Key bits field shown when type=${this.type}`);
|
||||
|
||||
this.set('type', 'internal');
|
||||
await fillIn(SELECTORS.typeField, this.type);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'This certificate type is internal. This means that the private key will not be returned and cannot be retrieved later. Below, you will name the key and define its type and key bits.',
|
||||
`has correct description for type=${this.type}`
|
||||
);
|
||||
assert.strictEqual(this.model.type, this.type);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 3 }, '3 form fields under keyParams toggle');
|
||||
assert.dom(SELECTORS.fieldByName('keyName')).exists(`Key name field shown when type=${this.type}`);
|
||||
assert.dom(SELECTORS.fieldByName('keyType')).exists(`Key type field shown when type=${this.type}`);
|
||||
assert.dom(SELECTORS.fieldByName('keyBits')).exists(`Key bits field shown when type=${this.type}`);
|
||||
|
||||
this.set('type', 'existing');
|
||||
await fillIn(SELECTORS.typeField, this.type);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'You chose to use an existing key. This means that we’ll use the key reference to create the CSR or root. Please provide the reference to the key.',
|
||||
`has correct description for type=${this.type}`
|
||||
);
|
||||
assert.strictEqual(this.model.type, this.type);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 1 }, '1 form field under keyParams toggle');
|
||||
assert.dom(SELECTORS.fieldByName('keyRef')).exists(`Key reference field shown when type=${this.type}`);
|
||||
|
||||
this.set('type', 'kms');
|
||||
await fillIn(SELECTORS.typeField, this.type);
|
||||
assert
|
||||
.dom(SELECTORS.toggleGroupDescription)
|
||||
.hasText(
|
||||
'This certificate type is kms, meaning managed keys will be used. Below, you will name the key and tell Vault where to find it in your KMS or HSM. Learn more about managed keys.',
|
||||
`has correct description for type=${this.type}`
|
||||
);
|
||||
assert.strictEqual(this.model.type, this.type);
|
||||
assert
|
||||
.dom(`[data-test-group="Key parameters"] ${SELECTORS.formField}`)
|
||||
.exists({ count: 3 }, '3 form fields under keyParams toggle');
|
||||
assert.dom(SELECTORS.fieldByName('keyName')).exists(`Key name field shown when type=${this.type}`);
|
||||
assert
|
||||
.dom(SELECTORS.fieldByName('managedKeyName'))
|
||||
.exists(`Managed key name field shown when type=${this.type}`);
|
||||
assert
|
||||
.dom(SELECTORS.fieldByName('managedKeyId'))
|
||||
.exists(`Managed key id field shown when type=${this.type}`);
|
||||
});
|
||||
|
||||
test('it shows errors before submit if form is invalid', async function (assert) {
|
||||
const saveSpy = Sinon.spy();
|
||||
this.set('onSave', saveSpy);
|
||||
await render(
|
||||
hbs`<PkiGenerateRoot @model={{this.model}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
|
||||
{
|
||||
owner: this.engine,
|
||||
}
|
||||
);
|
||||
|
||||
await click(SELECTORS.saveButton);
|
||||
assert.dom(SELECTORS.formInvalidError).exists('Shows overall error form');
|
||||
assert.ok(saveSpy.notCalled);
|
||||
});
|
||||
});
|
|
@ -3,7 +3,7 @@ import { setupTest } from 'vault/tests/helpers';
|
|||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
|
||||
module('Unit | Adapter | pki/config', function (hooks) {
|
||||
module('Unit | Adapter | pki/action', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
|
@ -16,11 +16,11 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
test('it exists', function (assert) {
|
||||
const adapter = this.owner.lookup('adapter:pki/config');
|
||||
const adapter = this.owner.lookup('adapter:pki/action');
|
||||
assert.ok(adapter);
|
||||
});
|
||||
|
||||
module('formType import', function (hooks) {
|
||||
module('actionType import', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.payload = {
|
||||
pem_bundle: `-----BEGIN CERTIFICATE REQUEST-----
|
||||
|
@ -51,8 +51,8 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', this.payload)
|
||||
.save({ adapterOptions: { formType: 'import', useIssuer: false } });
|
||||
.createRecord('pki/action', this.payload)
|
||||
.save({ adapterOptions: { actionType: 'import', useIssuer: false } });
|
||||
});
|
||||
|
||||
test('it calls the correct endpoint when useIssuer = true', async function (assert) {
|
||||
|
@ -63,15 +63,15 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', this.payload)
|
||||
.save({ adapterOptions: { formType: 'import', useIssuer: true } });
|
||||
.createRecord('pki/action', this.payload)
|
||||
.save({ adapterOptions: { actionType: 'import', useIssuer: true } });
|
||||
});
|
||||
});
|
||||
|
||||
module('formType generate-root', function () {
|
||||
module('actionType generate-root', function () {
|
||||
test('it calls the correct endpoint when useIssuer = false', async function (assert) {
|
||||
assert.expect(4);
|
||||
const adapterOptions = { adapterOptions: { formType: 'generate-root', useIssuer: false } };
|
||||
const adapterOptions = { adapterOptions: { actionType: 'generate-root', useIssuer: false } };
|
||||
this.server.post(`${this.backend}/root/generate/internal`, () => {
|
||||
assert.ok(true, 'request made correctly when type = internal');
|
||||
return {};
|
||||
|
@ -90,22 +90,22 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'internal',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'exported',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'existing',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'kms',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
|
@ -113,7 +113,7 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
|
||||
test('it calls the correct endpoint when useIssuer = true', async function (assert) {
|
||||
assert.expect(4);
|
||||
const adapterOptions = { adapterOptions: { formType: 'generate-root', useIssuer: true } };
|
||||
const adapterOptions = { adapterOptions: { actionType: 'generate-root', useIssuer: true } };
|
||||
this.server.post(`${this.backend}/issuers/generate/root/internal`, () => {
|
||||
assert.ok(true, 'request made correctly when type = internal');
|
||||
return {};
|
||||
|
@ -132,32 +132,32 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'internal',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'exported',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'existing',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'kms',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
});
|
||||
});
|
||||
|
||||
module('formType generate-csr', function () {
|
||||
module('actionType generate-csr', function () {
|
||||
test('it calls the correct endpoint when useIssuer = false', async function (assert) {
|
||||
assert.expect(4);
|
||||
const adapterOptions = { adapterOptions: { formType: 'generate-csr', useIssuer: false } };
|
||||
const adapterOptions = { adapterOptions: { actionType: 'generate-csr', useIssuer: false } };
|
||||
this.server.post(`${this.backend}/intermediate/generate/internal`, () => {
|
||||
assert.ok(true, 'request made correctly when type = internal');
|
||||
return {};
|
||||
|
@ -176,22 +176,22 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'internal',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'exported',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'existing',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'kms',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
|
@ -199,7 +199,7 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
|
||||
test('it calls the correct endpoint when useIssuer = true', async function (assert) {
|
||||
assert.expect(4);
|
||||
const adapterOptions = { adapterOptions: { formType: 'generate-csr', useIssuer: true } };
|
||||
const adapterOptions = { adapterOptions: { actionType: 'generate-csr', useIssuer: true } };
|
||||
this.server.post(`${this.backend}/issuers/generate/intermediate/internal`, () => {
|
||||
assert.ok(true, 'request made correctly when type = internal');
|
||||
return {};
|
||||
|
@ -218,22 +218,22 @@ module('Unit | Adapter | pki/config', function (hooks) {
|
|||
});
|
||||
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'internal',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'exported',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'existing',
|
||||
})
|
||||
.save(adapterOptions);
|
||||
await this.store
|
||||
.createRecord('pki/config', {
|
||||
.createRecord('pki/action', {
|
||||
type: 'kms',
|
||||
})
|
||||
.save(adapterOptions);
|
|
@ -18,6 +18,11 @@ const createClass = (propertyNames, groups) => {
|
|||
subText: 'Maybe a checkbox',
|
||||
})
|
||||
bar;
|
||||
@attr('number', {
|
||||
label: 'Baz',
|
||||
subText: 'A number field',
|
||||
})
|
||||
baz;
|
||||
}
|
||||
return new Foo();
|
||||
};
|
||||
|
@ -37,6 +42,11 @@ module('Unit | Decorators | ModelFormFields', function (hooks) {
|
|||
options: { label: 'Bar', subText: 'Maybe a checkbox' },
|
||||
type: 'boolean',
|
||||
};
|
||||
this.bazField = {
|
||||
name: 'baz',
|
||||
options: { label: 'Baz', subText: 'A number field' },
|
||||
type: 'number',
|
||||
};
|
||||
});
|
||||
hooks.afterEach(function () {
|
||||
this.spy.restore();
|
||||
|
@ -50,15 +60,14 @@ module('Unit | Decorators | ModelFormFields', function (hooks) {
|
|||
assert.ok(this.spy.calledWith(message), 'Error is printed to console');
|
||||
});
|
||||
|
||||
test('it should throw error when arguments are not provided', function (assert) {
|
||||
test('it return allFields when arguments not provided', function (assert) {
|
||||
assert.expect(1);
|
||||
try {
|
||||
createClass();
|
||||
} catch (error) {
|
||||
const message =
|
||||
'Array of property names and/or array of field groups are required when using withFormFields model decorator';
|
||||
assert.strictEqual(error.message, message);
|
||||
}
|
||||
const model = createClass();
|
||||
assert.deepEqual(
|
||||
[this.fooField, this.barField, this.bazField],
|
||||
model.allFields,
|
||||
'allFields set on Model class'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should set formFields prop on Model class', function (assert) {
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'vault/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | pki/config', function (hooks) {
|
||||
module('Unit | Serializer | pki/action', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('pki/config');
|
||||
const serializer = store.serializerFor('pki/action');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('pki/config', {});
|
||||
const record = store.createRecord('pki/action', {});
|
||||
|
||||
const serializedRecord = record.serialize();
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import Model from '@ember-data/model';
|
||||
import PkiActionModel from 'vault/models/pki/action';
|
||||
import PkiCertificateGenerateModel from 'vault/models/pki/certificate/generate';
|
||||
import PkiConfigImportModel from 'vault/models/pki/config/import';
|
||||
|
||||
declare module 'ember-data/types/registries/model' {
|
||||
export default interface ModelRegistry {
|
||||
'pki/config/import': PkiConfigImportModel;
|
||||
'pki/action': PkiActionModel;
|
||||
'pki/certificate/generate': PkiCertificateGenerateModel;
|
||||
// Catchall for any other models
|
||||
[key: string]: any;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Model from '@ember-data/model';
|
||||
|
||||
export default class PkiConfigModel extends Model {
|
||||
export default class PkiActionModel extends Model {
|
||||
secretMountPath: unknown;
|
||||
pemBundle: string;
|
||||
type: string;
|
Loading…
Reference in New Issue