UI: Move useOpenApi and getHelpUrl methods to util (#27764)

* Add map between model types and helpUrls, update tests

* replace modelProto.getHelpUrl with new helper util

* Remove all useOpenApi and getHelpUrl instances from models

* Add missing auth config model type
This commit is contained in:
Chelsea Shaw
2024-07-15 10:57:06 -05:00
committed by GitHub
parent 3d4f00a1d5
commit d2116025be
31 changed files with 64 additions and 106 deletions

View File

@@ -7,7 +7,4 @@ import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
backend: belongsTo('auth-method', { inverse: 'authConfigs', readOnly: true, async: false }),
getHelpUrl: function (backend) {
return `/v1/auth/${backend}/config?help=1`;
},
});

View File

@@ -10,7 +10,6 @@ import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
tenantId: attr('string', {
label: 'Tenant ID',
helpText: 'The tenant ID for the Azure Active Directory organization',

View File

@@ -10,7 +10,6 @@ import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
// We have to leave this here because the backend doesn't support the file type yet.
credentials: attr('string', {
editType: 'file',

View File

@@ -10,7 +10,6 @@ import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
organization: attr('string'),
baseUrl: attr('string', {
label: 'Base URL',

View File

@@ -10,7 +10,6 @@ import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
oidcDiscoveryUrl: attr('string', {
label: 'OIDC discovery URL',
helpText:

View File

@@ -11,7 +11,6 @@ import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
kubernetesHost: attr('string', {
helpText:
'Host must be a host string, a host:port pair, or a URL to the base of the Kubernetes API server.',

View File

@@ -11,7 +11,6 @@ import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
certificate: attr({
label: 'Certificate',
editType: 'file',

View File

@@ -10,7 +10,6 @@ import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
orgName: attr('string', {
helpText: 'Name of the organization to be used in the Okta API',
}),

View File

@@ -10,7 +10,6 @@ import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
useOpenAPI: true,
host: attr('string'),
secret: attr('string'),

View File

@@ -9,11 +9,7 @@ import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default Model.extend({
useOpenAPI: true,
ca: belongsTo('kmip/ca', { async: false, inverse: 'config' }),
getHelpUrl(path) {
return `/v1/${path}/config?help=1`;
},
fieldGroups: computed('newFields', function () {
let groups = [{ default: ['listenAddrs', 'connectionTimeout'] }];

View File

@@ -36,13 +36,10 @@ export const COMPUTEDS = {
};
export default Model.extend(COMPUTEDS, {
useOpenAPI: true,
backend: attr({ readOnly: true }),
scope: attr({ readOnly: true }),
name: attr({ readOnly: true }),
getHelpUrl(path) {
return `/v1/${path}/scope/example/role/example?help=1`;
},
fieldGroups: computed('fields', 'defaultFields.length', 'tlsFields', function () {
const groups = [{ TLS: this.tlsFields }];
if (this.defaultFields.length) {

View File

@@ -4,7 +4,6 @@
*/
import Model, { attr } from '@ember-data/model';
import { assert } from '@ember/debug';
import { service } from '@ember/service';
import { withFormFields } from 'vault/decorators/model-form-fields';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@@ -25,15 +24,9 @@ const certDisplayFields = ['certificate', 'commonName', 'revocationTime', 'seria
export default class PkiCertificateBaseModel extends Model {
@service secretMountPath;
get useOpenAPI() {
return true;
}
get backend() {
return this.secretMountPath.currentPath;
}
getHelpUrl() {
assert('You must provide a helpUrl for OpenAPI', true);
}
// The attributes parsed from parse-pki-cert util live here
@attr parsedCertificate;

View File

@@ -34,8 +34,5 @@ const certDisplayFields = [
];
@withFormFields(certDisplayFields, generateFromRole)
export default class PkiCertificateGenerateModel extends PkiCertificateBaseModel {
getHelpUrl(backend) {
return `/v1/${backend}/issue/example?help=1`;
}
@attr('string') role; // role name to issue certificate against for request URL
}

View File

@@ -23,9 +23,6 @@ const generateFromRole = [
];
@withFormFields(null, generateFromRole)
export default class PkiCertificateSignModel extends PkiCertificateBaseModel {
getHelpUrl(backend) {
return `/v1/${backend}/sign/example?help=1`;
}
@attr('string') role; // role name to create certificate against for request URL
@attr('string', {

View File

@@ -10,16 +10,8 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@withFormFields()
export default class PkiConfigAcmeModel extends Model {
// This model uses the backend value as the model ID
get useOpenAPI() {
return true;
}
getHelpUrl(backendPath) {
return `/v1/${backendPath}/config/acme?help=1`;
}
// attrs order in the form is determined by order here
@attr('boolean', {
label: 'ACME enabled',
subText: 'When ACME is disabled, all requests to ACME directory URLs will return 404.',

View File

@@ -10,13 +10,6 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@withFormFields()
export default class PkiConfigClusterModel extends Model {
// This model uses the backend value as the model ID
get useOpenAPI() {
return true;
}
getHelpUrl(backendPath) {
return `/v1/${backendPath}/config/cluster?help=1`;
}
@attr('string', {
label: "Mount's API path",

View File

@@ -10,12 +10,6 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@withFormFields()
export default class PkiConfigUrlsModel extends Model {
// This model uses the backend value as the model ID
get useOpenAPI() {
return true;
}
getHelpUrl(backendPath) {
return `/v1/${backendPath}/config/urls?help=1`;
}
@attr({
label: 'Issuing certificates',

View File

@@ -27,10 +27,7 @@ const displayFields = [
@withFormFields(inputFields, displayFields)
export default class PkiIssuerModel extends Model {
@service secretMountPath;
// TODO use openAPI after removing route extension (see pki/roles route for example)
get useOpenAPI() {
return false;
}
get backend() {
return this.secretMountPath.currentPath;
}

View File

@@ -18,14 +18,6 @@ const validations = {
export default class PkiRoleModel extends Model {
@service version; // noStoreMetadata is enterprise-only, so we need this available
get useOpenAPI() {
// must be a getter so it can be accessed in path-help.js
return true;
}
getHelpUrl(backend) {
return `/v1/${backend}/roles/example?help=1`;
}
@attr('string', { readOnly: true }) backend;
get formFieldGroups() {

View File

@@ -25,10 +25,6 @@ const validations = {
'maxPathLength',
])
export default class PkiSignIntermediateModel extends PkiCertificateBaseModel {
getHelpUrl(backend) {
return `/v1/${backend}/issuer/example/sign-intermediate?help=1`;
}
@attr issuerRef;
@attr('string', {

View File

@@ -125,14 +125,6 @@ export default class PkiTidyModel extends Model {
})
tidyRevokedCerts;
get useOpenAPI() {
return true;
}
getHelpUrl(backend) {
return `/v1/${backend}/config/auto-tidy?help=1`;
}
get allGroups() {
const groups = [{ autoTidy: ['enabled', 'intervalDuration'] }, ...this.sharedFields];
return this._expandGroups(groups);

View File

@@ -47,10 +47,6 @@ const CA_FIELDS = [
];
export default Model.extend({
useOpenAPI: true,
getHelpUrl: function (backend) {
return `/v1/${backend}/roles/example?help=1`;
},
zeroAddress: attr('boolean', {
readOnly: true,
}),

View File

@@ -26,6 +26,7 @@ import {
filterPathsByItemType,
pathToHelpUrlSegment,
reducePathsByPathName,
getHelpUrlForModel,
} from 'vault/utils/openapi-helpers';
import { isPresent } from '@ember/utils';
@@ -53,17 +54,17 @@ export default Service.extend({
const modelName = `model:${modelType}`;
const modelFactory = owner.factoryFor(modelName);
let newModel, helpUrl;
let helpUrl = getHelpUrlForModel(modelType, backend);
let newModel;
// if we have a factory, we need to take the existing model into account
if (modelFactory) {
debug(`Model factory found for ${modelType}`);
newModel = modelFactory.class;
const modelProto = newModel.proto();
if (newModel.merged || modelProto.useOpenAPI !== true) {
if (newModel.merged || !helpUrl) {
return resolve();
}
helpUrl = modelProto.getHelpUrl(backend);
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);
} else {
debug(`Creating new Model for ${modelType}`);
@@ -73,7 +74,6 @@ export default Service.extend({
// we don't have an apiPath for dynamic secrets
// and we don't need paths for them yet
if (!apiPath) {
helpUrl = newModel.proto().getHelpUrl(backend);
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);
}
@@ -98,7 +98,7 @@ export default Service.extend({
return reject();
}
helpUrl = `/v1/${apiPath}${path.slice(1)}?help=true` || newModel.proto().getHelpUrl(backend);
helpUrl = `/v1/${apiPath}${path.slice(1)}?help=true`;
pathInfo.paths = paths;
newModel = newModel.extend({ paths: pathInfo });
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);

View File

@@ -132,3 +132,37 @@ export function filterPathsByItemType(pathInfo: PathsInfo, itemType: string): Pa
return itemType === path.itemType;
});
}
/**
* This object maps model names to the openAPI path that hydrates the model, given the backend path.
*/
const OPENAPI_POWERED_MODELS = {
'role-ssh': (backend: string) => `/v1/${backend}/roles/example?help=1`,
'auth-config/azure': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/cert': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/gcp': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/github': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/jwt': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/kubernetes': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/ldap': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/okta': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/radius': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'kmip/config': (backend: string) => `/v1/${backend}/config?help=1`,
'kmip/role': (backend: string) => `/v1/${backend}/scope/example/role/example?help=1`,
'pki/role': (backend: string) => `/v1/${backend}/roles/example?help=1`,
'pki/tidy': (backend: string) => `/v1/${backend}/config/auto-tidy?help=1`,
'pki/sign-intermediate': (backend: string) => `/v1/${backend}/issuer/example/sign-intermediate?help=1`,
'pki/certificate/generate': (backend: string) => `/v1/${backend}/issue/example?help=1`,
'pki/certificate/sign': (backend: string) => `/v1/${backend}/sign/example?help=1`,
'pki/config/acme': (backend: string) => `/v1/${backend}/config/acme?help=1`,
'pki/config/cluster': (backend: string) => `/v1/${backend}/config/cluster?help=1`,
'pki/config/urls': (backend: string) => `/v1/${backend}/config/urls?help=1`,
};
export function getHelpUrlForModel(modelType: string, backend: string) {
const urlFn = OPENAPI_POWERED_MODELS[modelType as keyof typeof OPENAPI_POWERED_MODELS] as (
backend: string
) => string;
if (!urlFn) return null;
return urlFn(backend);
}

View File

@@ -9,6 +9,7 @@ import authPage from 'vault/tests/pages/auth';
import { deleteAuthCmd, deleteEngineCmd, mountAuthCmd, mountEngineCmd, runCmd } from '../helpers/commands';
import expectedSecretAttrs from 'vault/tests/helpers/openapi/expected-secret-attrs';
import expectedAuthAttrs from 'vault/tests/helpers/openapi/expected-auth-attrs';
import { getHelpUrlForModel } from 'vault/utils/openapi-helpers';
/**
* This set of tests is for ensuring that backend changes to the OpenAPI spec
@@ -72,8 +73,7 @@ function secretEngineHelper(test, secretEngine) {
// A given secret engine might have multiple models that are openApi driven
modelNames.forEach((modelName) => {
test(`${modelName} model getProps returns correct attributes`, async function (assert) {
const model = this.store.createRecord(modelName, {});
const helpUrl = model.getHelpUrl(this.backend);
const helpUrl = getHelpUrlForModel(modelName, this.backend);
const result = await this.pathHelp.getProps(helpUrl, this.backend);
const expected = engineData[modelName];
// Expected values should be updated to match "actual" (result)
@@ -98,8 +98,7 @@ function authEngineHelper(test, authBackend) {
if (itemName.startsWith('auth-config/')) {
// Config test doesn't need to instantiate a new model
test(`${itemName} model`, async function (assert) {
const model = this.store.createRecord(itemName, {});
const helpUrl = model.getHelpUrl(this.mount);
const helpUrl = getHelpUrlForModel(itemName, this.mount);
const result = await this.pathHelp.getProps(helpUrl, this.mount);
const expected = authData[itemName];
assert.deepEqual(
@@ -116,9 +115,8 @@ function authEngineHelper(test, authBackend) {
const modelName = `generated-${itemName}-${authBackend}`;
// Generated items need to instantiate the model first via getNewModel
await this.pathHelp.getNewModel(modelName, this.mount, `auth/${this.mount}/`, itemName);
const model = this.store.createRecord(modelName, {});
// Generated items don't have this method -- helpUrl is calculated in path-help.js line 101
const helpUrl = model.getHelpUrl(this.mount);
// Generated items don't have helpUrl method -- helpUrl is calculated in path-help.js line 101
const helpUrl = `/v1/auth/${this.mount}?help=1`;
const result = await this.pathHelp.getProps(helpUrl, this.mount);
const expected = authData[modelName];
assert.deepEqual(result, expected, `getProps returns expected attributes for ${modelName}`);

View File

@@ -4,11 +4,10 @@
*/
import { module, test } from 'qunit';
import { _getPathParam, pathToHelpUrlSegment } from 'vault/utils/openapi-helpers';
import { _getPathParam, getHelpUrlForModel, pathToHelpUrlSegment } from 'vault/utils/openapi-helpers';
module('Unit | Utility | OpenAPI helper utils', function () {
test(`pathToHelpUrlSegment`, function (assert) {
assert.expect(5);
[
{ path: '/auth/{username}', result: '/auth/example' },
{ path: '{username}/foo', result: 'example/foo' },
@@ -21,7 +20,6 @@ module('Unit | Utility | OpenAPI helper utils', function () {
});
test(`_getPathParam`, function (assert) {
assert.expect(7);
[
{ path: '/auth/{username}', result: 'username' },
{ path: '{unicorn}/foo', result: 'unicorn' },
@@ -34,4 +32,20 @@ module('Unit | Utility | OpenAPI helper utils', function () {
assert.strictEqual(_getPathParam(test.path), test.result, `returns first param for ${test.path}`);
});
});
test(`getHelpUrlForModel`, function (assert) {
[
{ modelType: 'kmip/config', result: '/v1/foobar/config?help=1' },
{ modelType: 'does-not-exist', result: null },
{ modelType: 4, result: null },
{ modelType: '', result: null },
{ modelType: undefined, result: null },
].forEach((test) => {
assert.strictEqual(
getHelpUrlForModel(test.modelType, 'foobar'),
test.result,
`returns first param for ${test.path}`
);
});
});
});

View File

@@ -6,9 +6,7 @@
import Model from '@ember-data/model';
export default class PkiCertificateBaseModel extends Model {
secretMountPath: class;
get useOpenAPI(): boolean;
get backend(): string;
getHelpUrl(): void;
altNames: string;
commonName: string;
caChain: string;

View File

@@ -6,8 +6,6 @@
import Model from '@ember-data/model';
export default class PkiConfigUrlsModel extends Model {
get useOpenAPI(): boolean;
getHelpUrl(backendPath: string): string;
issuingCertificates: array;
crlDistributionPoints: array;
ocspServers: array;

View File

@@ -8,7 +8,6 @@ import { FormField, FormFieldGroups, ModelValidations } from 'vault/app-types';
import { ParsedCertificateData } from 'vault/vault/utils/parse-pki-cert';
export default class PkiIssuerModel extends Model {
secretMountPath: class;
get useOpenAPI(): boolean;
get backend(): string;
get issuerRef(): string;
certificate: string;

View File

@@ -7,10 +7,8 @@ import Model from '@ember-data/model';
import { ModelValidations } from 'vault/app-types';
export default class PkiRoleModel extends Model {
get useOpenAPI(): boolean;
name: string;
issuerRef: string;
getHelpUrl(backendPath: string): string;
validate(): ModelValidations;
isNew: boolean;
keyType: string;

View File

@@ -24,8 +24,6 @@ export default class PkiTidyModel extends Model {
tidyRevocationQueue: boolean;
tidyRevokedCertIssuerAssociations: boolean;
tidyRevokedCerts: boolean;
get useOpenAPI(): boolean;
getHelpUrl(backend: string): string;
allByKey: {
intervalDuration: FormField[];
};