mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Request validator plugin respects existing properties and generates required ones (#3283)
This commit is contained in:
parent
40deacb718
commit
874f6c9b27
@ -0,0 +1,93 @@
|
||||
{
|
||||
"_format_version": "1.1",
|
||||
"services": [
|
||||
{
|
||||
"name": "Example",
|
||||
"plugins": [],
|
||||
"routes": [
|
||||
{
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"name": "Example-body-post",
|
||||
"paths": [
|
||||
"/body$"
|
||||
],
|
||||
"plugins": [
|
||||
{
|
||||
"config": {
|
||||
"allowed_content_types": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"body_schema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"}}}",
|
||||
"verbose_response": true,
|
||||
"version": "draft4"
|
||||
},
|
||||
"enabled": true,
|
||||
"name": "request-validator"
|
||||
}
|
||||
],
|
||||
"strip_path": false,
|
||||
"tags": [
|
||||
"OAS3_import",
|
||||
"OAS3file_request-validator-plugin.yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"name": "Example-params-get",
|
||||
"paths": [
|
||||
"/params$"
|
||||
],
|
||||
"plugins": [
|
||||
{
|
||||
"config": {
|
||||
"parameter_schema": [
|
||||
{
|
||||
"explode": false,
|
||||
"in": "path",
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"schema": "{\"type\":\"integer\"}",
|
||||
"style": "form"
|
||||
}
|
||||
],
|
||||
"verbose_response": true,
|
||||
"version": "draft4"
|
||||
},
|
||||
"enabled": true,
|
||||
"name": "request-validator"
|
||||
}
|
||||
],
|
||||
"strip_path": false,
|
||||
"tags": [
|
||||
"OAS3_import",
|
||||
"OAS3file_request-validator-plugin.yaml"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"OAS3_import",
|
||||
"OAS3file_request-validator-plugin.yaml"
|
||||
],
|
||||
"url": "http://backend.com/path"
|
||||
}
|
||||
],
|
||||
"upstreams": [
|
||||
{
|
||||
"name": "Example",
|
||||
"tags": [
|
||||
"OAS3_import",
|
||||
"OAS3file_request-validator-plugin.yaml"
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"target": "backend.com:80"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
openapi: 3.0.2
|
||||
|
||||
info:
|
||||
title: Example
|
||||
version: 1.0.0
|
||||
|
||||
servers:
|
||||
- url: http://backend.com/path
|
||||
|
||||
paths:
|
||||
/params:
|
||||
get:
|
||||
x-kong-plugin-request-validator:
|
||||
enabled: true
|
||||
config:
|
||||
verbose_response: true
|
||||
parameters:
|
||||
- in: path
|
||||
name: userId
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
/body:
|
||||
post:
|
||||
x-kong-plugin-request-validator:
|
||||
enabled: true
|
||||
config:
|
||||
verbose_response: true
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/jsonSchema'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/xmlSchema'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
jsonSchema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
xmlSchema:
|
||||
type: object
|
||||
properties:
|
||||
prop:
|
||||
type: integer
|
@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { generateServerPlugins, generatePlugin } from '../plugins';
|
||||
import { generateServerPlugins, generatePlugin, generateRequestValidatorPlugin } from '../plugins';
|
||||
|
||||
describe('plugins', () => {
|
||||
describe('generateServerPlugins()', () => {
|
||||
@ -63,4 +63,306 @@ describe('plugins', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateRequestValidatorPlugin()', () => {
|
||||
const parameterSchema = [
|
||||
{
|
||||
schema: '{"anyOf":[{"type":"string"}]}',
|
||||
style: 'form',
|
||||
in: 'path',
|
||||
name: 'human_timestamp',
|
||||
required: true,
|
||||
explode: false,
|
||||
},
|
||||
];
|
||||
|
||||
it('should retain config properties', () => {
|
||||
const plugin = {
|
||||
enabled: true,
|
||||
config: {
|
||||
parameter_schema: [parameterSchema],
|
||||
body_schema: '[{"name":{"type": "string", "required": true}}]',
|
||||
verbose_response: true,
|
||||
allowed_content_types: ['application/json'],
|
||||
},
|
||||
};
|
||||
|
||||
const operation = {};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated).toStrictEqual({
|
||||
name: 'request-validator',
|
||||
enabled: plugin.enabled,
|
||||
config: { version: 'draft4', ...plugin.config },
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add properties if they are not defined', () => {
|
||||
const plugin = {
|
||||
enabled: true,
|
||||
config: {
|
||||
parameter_schema: [parameterSchema],
|
||||
body_schema: '[{"name":{"type": "string", "required": true}}]',
|
||||
// The following properties are missing
|
||||
// verbose_response: true,
|
||||
// allowed_content_types: ['application/json'],
|
||||
},
|
||||
};
|
||||
|
||||
const operation = {};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated).toStrictEqual({
|
||||
name: 'request-validator',
|
||||
enabled: plugin.enabled,
|
||||
config: { version: 'draft4', ...plugin.config },
|
||||
});
|
||||
});
|
||||
|
||||
describe('parameter_schema', () => {
|
||||
it('should not add parameter_schema if no parameters present', () => {
|
||||
const plugin = {
|
||||
enabled: true,
|
||||
config: {},
|
||||
};
|
||||
|
||||
const generated1 = generateRequestValidatorPlugin(plugin, {});
|
||||
const generated2 = generateRequestValidatorPlugin(plugin, { parameters: [] });
|
||||
|
||||
expect(generated1.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
});
|
||||
expect(generated2.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert operation parameters to parameter_schema', () => {
|
||||
const plugin = {
|
||||
config: {},
|
||||
};
|
||||
|
||||
const param = {
|
||||
in: 'query',
|
||||
explode: true,
|
||||
required: false,
|
||||
name: 'some_name',
|
||||
schema: {
|
||||
anyOf: [{ type: 'string' }],
|
||||
},
|
||||
style: 'form',
|
||||
};
|
||||
|
||||
const operation: OA3Operation = {
|
||||
parameters: [param],
|
||||
};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
parameter_schema: [
|
||||
{
|
||||
schema: '{"anyOf":[{"type":"string"}]}',
|
||||
style: param.style,
|
||||
in: param.in,
|
||||
name: param.name,
|
||||
explode: param.explode,
|
||||
required: param.required,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return default if operation parameter schema not defined on any parameters', () => {
|
||||
const plugin = {};
|
||||
|
||||
const operation: OA3Operation = {
|
||||
parameters: [
|
||||
{
|
||||
in: 'query',
|
||||
name: 'some_name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
parameter_schema: [
|
||||
{
|
||||
explode: false,
|
||||
in: 'query',
|
||||
name: 'some_name',
|
||||
required: false,
|
||||
schema: '{}',
|
||||
style: 'form',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore parameters without schema', () => {
|
||||
const plugin = {};
|
||||
const paramWithSchema = {
|
||||
in: 'query',
|
||||
explode: true,
|
||||
required: false,
|
||||
name: 'some_name',
|
||||
schema: {
|
||||
anyOf: [{ type: 'string' }],
|
||||
},
|
||||
style: 'form',
|
||||
};
|
||||
const paramWithoutSchema = {
|
||||
in: 'query',
|
||||
name: 'some_name',
|
||||
};
|
||||
const operation: OA3Operation = {
|
||||
parameters: [paramWithSchema, paramWithoutSchema],
|
||||
};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
parameter_schema: [
|
||||
{
|
||||
schema: '{"anyOf":[{"type":"string"}]}',
|
||||
style: paramWithSchema.style,
|
||||
in: paramWithSchema.in,
|
||||
name: paramWithSchema.name,
|
||||
explode: paramWithSchema.explode,
|
||||
required: paramWithSchema.required,
|
||||
},
|
||||
{
|
||||
schema: '{}',
|
||||
style: 'form',
|
||||
in: paramWithoutSchema.in,
|
||||
name: paramWithoutSchema.name,
|
||||
explode: false,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('body_schema and allowed_content_types', () => {
|
||||
it('should not add body_schema or allowed_content_types if no body present', () => {
|
||||
const plugin = {
|
||||
config: {},
|
||||
};
|
||||
|
||||
const operation = {};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return default if no operation request body content defined', () => {
|
||||
const plugin = {};
|
||||
|
||||
const defaultReqVal = {
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
};
|
||||
|
||||
const op1 = { requestBody: {} };
|
||||
const op2 = { requestBody: { $ref: 'non-existent' } };
|
||||
const op3 = {};
|
||||
|
||||
expect(generateRequestValidatorPlugin(plugin, op1).config).toStrictEqual(defaultReqVal);
|
||||
expect(generateRequestValidatorPlugin(plugin, op2).config).toStrictEqual(defaultReqVal);
|
||||
expect(generateRequestValidatorPlugin(plugin, op3).config).toStrictEqual(defaultReqVal);
|
||||
});
|
||||
|
||||
it('should add non-json media types to allowed content types and not add body schema', () => {
|
||||
const plugin = {};
|
||||
|
||||
const operation: OA3Operation = {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/xml': {},
|
||||
'text/yaml': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
allowed_content_types: ['application/xml', 'text/yaml'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should add body_schema and allowed content types', () => {
|
||||
const plugin = {};
|
||||
|
||||
const schemaXml = {
|
||||
type: 'Object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'integer',
|
||||
format: 'int64',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const schemaJson = {
|
||||
type: 'Object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
format: 'int64',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const operation: OA3Operation = {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/xml': {
|
||||
schema: schemaXml,
|
||||
},
|
||||
'application/json': {
|
||||
schema: schemaJson,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: JSON.stringify(schemaJson),
|
||||
allowed_content_types: ['application/xml', 'application/json'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should default body_schema if no schema is defined or generated', () => {
|
||||
const plugin = {};
|
||||
const operation = {};
|
||||
|
||||
const generated = generateRequestValidatorPlugin(plugin, operation);
|
||||
|
||||
expect(generated.config).toStrictEqual({
|
||||
version: 'draft4',
|
||||
body_schema: '{}',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -34,52 +34,110 @@ export function generatePlugin(key: string, value: Object): DCPlugin {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export function generateRequestValidatorPlugin(obj: Object, operation: OA3Operation): DCPlugin {
|
||||
const config: { [string]: Object } = {
|
||||
version: 'draft4', // Fixed version
|
||||
};
|
||||
/*
|
||||
This is valid config to allow all content to pass
|
||||
See: https://github.com/Kong/kong-plugin-enterprise-request-validator/pull/34/files#diff-1a1d2d5ce801cc1cfb2aa91ae15686d81ef900af1dbef00f004677bc727bfd3cR284
|
||||
*/
|
||||
const ALLOW_ALL_SCHEMA = '{}';
|
||||
|
||||
config.parameter_schema = [];
|
||||
function generateParameterSchema(operation: OA3Operation): Array<Object> | typeof undefined {
|
||||
let parameterSchema;
|
||||
|
||||
if (operation.parameters) {
|
||||
if (operation.parameters?.length) {
|
||||
parameterSchema = [];
|
||||
for (const p of operation.parameters) {
|
||||
if (!(p: Object).schema) {
|
||||
throw new Error("Parameter using 'content' type validation is not supported");
|
||||
// The following is valid config to allow all content to pass, in the case where schema is not defined
|
||||
let schema;
|
||||
if ((p: Object).schema) {
|
||||
schema = JSON.stringify((p: Object).schema);
|
||||
} else if ((p: Object).content) {
|
||||
// only parameters defined with a schema (not content) are supported
|
||||
schema = ALLOW_ALL_SCHEMA;
|
||||
} else {
|
||||
// no schema or content property on a parameter is in violation with the OpenAPI spec
|
||||
schema = ALLOW_ALL_SCHEMA;
|
||||
}
|
||||
config.parameter_schema.push({
|
||||
|
||||
parameterSchema.push({
|
||||
in: (p: Object).in,
|
||||
explode: !!(p: Object).explode,
|
||||
required: !!(p: Object).required,
|
||||
name: (p: Object).name,
|
||||
schema: JSON.stringify((p: Object).schema),
|
||||
schema,
|
||||
style: (p: Object).style ?? 'form',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (operation.requestBody) {
|
||||
const content = (operation.requestBody: Object).content;
|
||||
if (!content) {
|
||||
throw new Error('content property is missing for request-validator!');
|
||||
}
|
||||
return parameterSchema;
|
||||
}
|
||||
|
||||
let bodySchema;
|
||||
for (const mediatype of Object.keys(content)) {
|
||||
if (mediatype !== 'application/json') {
|
||||
throw new Error(`Body validation supports only 'application/json', not ${mediatype}`);
|
||||
}
|
||||
const item = content[mediatype];
|
||||
function generateBodyOptions(
|
||||
operation: OA3Operation,
|
||||
): {
|
||||
bodySchema: string | typeof undefined,
|
||||
allowedContentTypes: Array<string> | typeof undefined,
|
||||
} {
|
||||
let bodySchema;
|
||||
let allowedContentTypes;
|
||||
|
||||
const bodyContent = (operation.requestBody: Object)?.content;
|
||||
if (bodyContent) {
|
||||
const jsonContentType = 'application/json';
|
||||
|
||||
allowedContentTypes = Object.keys(bodyContent);
|
||||
if (allowedContentTypes.includes(jsonContentType)) {
|
||||
const item = bodyContent[jsonContentType];
|
||||
bodySchema = JSON.stringify(item.schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (bodySchema) {
|
||||
config.body_schema = bodySchema;
|
||||
}
|
||||
return { bodySchema, allowedContentTypes };
|
||||
}
|
||||
|
||||
export function generateRequestValidatorPlugin(plugin: Object, operation: OA3Operation): DCPlugin {
|
||||
const config: { [string]: Object } = {
|
||||
version: 'draft4', // Fixed version
|
||||
};
|
||||
|
||||
const pluginConfig = plugin.config ?? {};
|
||||
|
||||
// Use original or generated parameter_schema
|
||||
const parameterSchema = pluginConfig.parameter_schema ?? generateParameterSchema(operation);
|
||||
|
||||
const generated = generateBodyOptions(operation);
|
||||
|
||||
// Use original or generated body_schema
|
||||
let bodySchema = pluginConfig.body_schema ?? generated.bodySchema;
|
||||
|
||||
// If no parameter_schema or body_schema is defined or generated, allow all content to pass
|
||||
if (parameterSchema === undefined && bodySchema === undefined) {
|
||||
bodySchema = ALLOW_ALL_SCHEMA;
|
||||
}
|
||||
|
||||
// Apply parameter_schema and body_schema to the config object
|
||||
if (parameterSchema !== undefined) {
|
||||
config.parameter_schema = parameterSchema;
|
||||
}
|
||||
|
||||
if (bodySchema !== undefined) {
|
||||
config.body_schema = bodySchema;
|
||||
}
|
||||
|
||||
// Use original or generated allowed_content_types
|
||||
const allowedContentTypes = pluginConfig.allowed_content_types ?? generated.allowedContentTypes;
|
||||
if (allowedContentTypes !== undefined) {
|
||||
config.allowed_content_types = allowedContentTypes;
|
||||
}
|
||||
|
||||
// Use original verbose_response if defined
|
||||
if (pluginConfig.verbose_response !== undefined) {
|
||||
config.verbose_response = Boolean(pluginConfig.verbose_response);
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
enabled: true,
|
||||
enabled: Boolean(plugin.enabled ?? true),
|
||||
name: 'request-validator',
|
||||
};
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ export function generateService(
|
||||
for (const routePath of Object.keys(api.paths)) {
|
||||
const pathItem: OA3PathItem = api.paths[routePath];
|
||||
|
||||
// TODO: Add path plugins to route
|
||||
for (const method of Object.keys(pathItem)) {
|
||||
if (
|
||||
method !== 'get' &&
|
||||
|
@ -37,9 +37,13 @@ declare type OA3Parameter = {|
|
||||
deprecated?: boolean,
|
||||
allowEmptyValue?: boolean,
|
||||
style?: 'form' | 'spaceDelimited' | 'pipeDelimited' | 'deepObject',
|
||||
schema?: Object,
|
||||
content?: Object,
|
||||
explode?: boolean,
|
||||
|};
|
||||
|
||||
declare type OA3RequestBody = {|
|
||||
content?: Object,
|
||||
// TODO
|
||||
|};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user