mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Prefix local references in mapping with a namespace (#189472)
**Resolves:** https://github.com/elastic/kibana/issues/188817 ## Summary This PR handles OpenAPI discriminator `mapping` (missing in https://github.com/elastic/kibana/pull/188812) field by prefixing local references with a namespace (see https://github.com/elastic/kibana/pull/188812 for more namespace details). It throws an error If mapping uses external references. ## How to test? Let's consider the following OpenAPI spec **spec1.schema.yaml** ```yaml openapi: 3.0.3 info: title: Spec1 version: '2023-10-31' paths: /api/some_api: get: responses: 200: content: 'application/json': schema: oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' - $ref: '#/components/schemas/Lizard' discriminator: propertyName: petType mapping: dog: '#/components/schemas/Dog' components: schemas: Pet: type: object required: [petType] properties: petType: type: string Cat: allOf: - $ref: '#/components/schemas/Pet' - type: object properties: name: type: string Dog: allOf: - $ref: '#/components/schemas/Pet' - type: object properties: bark: type: string Lizard: allOf: - $ref: '#/components/schemas/Pet' - type: object properties: lovesRocks: type: boolean ``` and a merging script ```js const { merge } = require('@kbn/openapi-bundler'); (async () => { await merge({ sourceGlobs: [ `path/to/spec1.schema.yaml`, ], outputFilePath: 'output.yaml, }); })(); ``` After running it it will produce the following bundler with references in `mapping` property prefixed with the spec title. ```yaml openapi: 3.0.3 info: title: Some title version: 1 paths: /api/some_api: get: responses: '200': content: application/json: schema: discriminator: mapping: dog: '#/components/schemas/Spec1_Dog' propertyName: petType oneOf: - $ref: '#/components/schemas/Spec1_Cat' - $ref: '#/components/schemas/Spec1_Dog' - $ref: '#/components/schemas/Spec1_Lizard' components: schemas: Spec1_Cat: allOf: - $ref: '#/components/schemas/Spec1_Pet' - type: object properties: name: type: string Spec1_Dog: allOf: - $ref: '#/components/schemas/Spec1_Pet' - type: object properties: bark: type: string Spec1_Lizard: allOf: - $ref: '#/components/schemas/Spec1_Pet' - type: object properties: lovesRocks: type: boolean Spec1_Pet: type: object properties: petType: type: string required: - petType ```
This commit is contained in:
parent
6c581d541b
commit
934d0b1cbc
6 changed files with 145 additions and 32 deletions
|
@ -10,6 +10,7 @@ import { extractByJsonPointer } from '../../../utils/extract_by_json_pointer';
|
|||
import { isPlainObjectType } from '../../../utils/is_plain_object_type';
|
||||
import { parseRef } from '../../../utils/parse_ref';
|
||||
import { DocumentNodeProcessor } from './types/document_node_processor';
|
||||
import { isLocalRef } from './utils/is_local_ref';
|
||||
|
||||
/**
|
||||
* Creates a node processor to prefix possibly conflicting components and security requirements
|
||||
|
@ -58,6 +59,23 @@ export function createNamespaceComponentsProcessor(pointer: string): DocumentNod
|
|||
// `components.securitySchemes`. It means items in `security` implicitly reference
|
||||
// `components.securitySchemes` items which should be handled.
|
||||
onNodeLeave(node, context) {
|
||||
// Handle mappings
|
||||
if (context.parentKey === 'mapping' && isPlainObjectType(node)) {
|
||||
for (const key of Object.keys(node)) {
|
||||
const maybeRef = node[key];
|
||||
|
||||
if (typeof maybeRef !== 'string' || !isLocalRef(maybeRef)) {
|
||||
throw new Error(
|
||||
`Expected mappings to have local references but got "${maybeRef}" in ${JSON.stringify(
|
||||
node
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
node[key] = decorateRefBaseName(maybeRef, namespace);
|
||||
}
|
||||
}
|
||||
|
||||
if ('security' in node && Array.isArray(node.security)) {
|
||||
for (const securityRequirements of node.security) {
|
||||
prefixObjectKeys(securityRequirements);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export function isLocalRef(ref: string): boolean {
|
||||
return ref.startsWith('#/');
|
||||
}
|
|
@ -22,9 +22,6 @@ export interface MergerConfig {
|
|||
outputFilePath: string;
|
||||
options?: {
|
||||
mergedSpecInfo?: Partial<OpenAPIV3.InfoObject>;
|
||||
conflictsResolution?: {
|
||||
prependComponentsWith: 'title';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -45,5 +45,5 @@ function getVersionedOutputFilePath(outputFilePath: string, version: string): st
|
|||
|
||||
const filename = basename(outputFilePath);
|
||||
|
||||
return outputFilePath.replace(filename, `${version}-${filename}`);
|
||||
return outputFilePath.replace(filename, `${version}_${filename}`);
|
||||
}
|
||||
|
|
|
@ -708,4 +708,89 @@ describe('OpenAPI Merger - merging specs with conflicting components', () => {
|
|||
Spec2_SomeCallback: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('prefixes discriminator mapping local references', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
info: {
|
||||
title: 'Spec1',
|
||||
version: '2023-10-31',
|
||||
},
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
oneOf: [
|
||||
{ $ref: '#/components/schemas/Component1' },
|
||||
{ $ref: '#/components/schemas/Component2' },
|
||||
],
|
||||
discriminator: {
|
||||
propertyName: 'commonProp',
|
||||
mapping: {
|
||||
component1: '#/components/schemas/Component1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
Component1: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
commonProp: {
|
||||
type: 'string',
|
||||
},
|
||||
extraProp1: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
Component2: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
commonProp: {
|
||||
type: 'string',
|
||||
},
|
||||
extraProp2: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [mergedSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
})
|
||||
);
|
||||
|
||||
expect(mergedSpec.paths['/api/some_api']?.get?.responses['200']).toMatchObject({
|
||||
content: {
|
||||
'application/json; Elastic-Api-Version=2023-10-31': {
|
||||
schema: expect.objectContaining({
|
||||
discriminator: expect.objectContaining({
|
||||
mapping: {
|
||||
component1: '#/components/schemas/Spec1_Component1',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mergedSpec.components?.schemas).toMatchObject({
|
||||
Spec1_Component1: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,34 +11,36 @@ const { join, resolve } = require('path');
|
|||
|
||||
const ROOT = resolve(__dirname, '../..');
|
||||
|
||||
bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/serverless/security_solution_detections_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
(async () => {
|
||||
await bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/serverless/security_solution_detections_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/ess/security_solution_detections_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
await bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/ess/security_solution_detections_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue