Skip to content

Commit

Permalink
fix read only check to inspect body request
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Oct 19, 2019
1 parent 72a3644 commit 5694276
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 54 deletions.
27 changes: 15 additions & 12 deletions src/middlewares/openapi.request.validator.ts
Expand Up @@ -63,8 +63,6 @@ export class RequestValidator {
requestBody = this._apiDocs.components.requestBodies[id];
}

this.validateReadOnlyProperties(path, requestBody, contentType);

let body = this.requestBodyToSchema(path, contentType, requestBody);
let requiredAdds = requestBody && requestBody.required ? ['body'] : [];

Expand All @@ -80,6 +78,7 @@ export class RequestValidator {
const validator = this.ajv.compile(schema);
return (req, res, next) => {
this.rejectUnknownQueryParams(req.query, schema.properties.query);
this.validateReadOnlyProperties(req.body, requestBody, contentType);

const shouldUpdatePathParams =
Object.keys(req.openapi.pathParams).length > 0;
Expand Down Expand Up @@ -166,12 +165,14 @@ export class RequestValidator {
}

private validateReadOnlyProperties(
path: string,
requestBody: any = {},
body: any = {},
requestBodySchema: any = {},
contentType: string,
) {
const bodySchema =
requestBody && requestBody.content && requestBody.content[contentType];
requestBodySchema &&
requestBodySchema.content &&
requestBodySchema.content[contentType];

const isRef = bodySchema && bodySchema.schema['$ref'];

Expand All @@ -186,16 +187,18 @@ export class RequestValidator {
const type = schema.type;
// string, number, integer, boolean, array, object
if (type === 'object') {
Object.keys(schema.properties || {}).forEach(itemKey => {
const readOnly = schema.properties[itemKey].hasOwnProperty(
'readOnly',
);
if (readOnly) {
throw validationError(400, path, message(itemKey));
Object.keys(schema.properties || {}).forEach(k => {
const readOnly = schema.properties[k].hasOwnProperty('readOnly');
if (body[k] && readOnly) {
throw validationError(400, `.body.${k}`, message(`${k}`));
}
});
} else if (schema.type !== 'object' && schema.readOnly) {
throw validationError(400, path, message('body'));
throw validationError(
400,
'.body',
message('body is a read-only property'),
);
}
// TODO handle inlined arrays
}
Expand Down
61 changes: 57 additions & 4 deletions test/read.only.spec.ts
Expand Up @@ -27,7 +27,17 @@ describe(packageJson.name, () => {
)
.post(`${app.basePath}/products/inlined`, (req, res) =>
res.json(req.body),
),
)
.post(`${app.basePath}/products/nested`, (req, res) => {
const body = req.body;
body.id = 'test';
body.created_at = new Date().toISOString();
body.reviews = body.reviews.map(r => ({
id: 99,
rating: r.rating || 2,
}));
res.json(body);
}),
);
});

Expand All @@ -39,11 +49,11 @@ describe(packageJson.name, () => {
request(app)
.post(`${app.basePath}/products`)
.set('content-type', 'application/json')
.query({
.send({
id: 'id_1',
name: 'some name',
price: 10.99,
created_at: new Date().toUTCString(),
created_at: new Date().toISOString(),
})
.expect(400)
.then(r => {
Expand All @@ -67,13 +77,56 @@ describe(packageJson.name, () => {
request(app)
.post(`${app.basePath}/products/inlined`)
.set('content-type', 'application/json')
.query({
.send({
id: 'id_1',
name: 'some name',
price: 10.99,
created_at: new Date().toUTCString(),
})
.expect(400)
.then(r => {
const body = r.body;
// id is a readonly property and should not be allowed in the request
expect(body.message).to.contain('id');
}));

it('should not allow read only properties in requests (nested schema $refs)', async () =>
request(app)
.post(`${app.basePath}/products/nested`)
.set('content-type', 'application/json')
.send({
id: 'id_1',
name: 'some name',
price: 10.99,
created_at: new Date().toISOString(),
reviews: {
id: 'review_id',
rating: 5,
},
})
.expect(400)
.then(r => {
const body = r.body;
console.log(body);
// id is a readonly property and should not be allowed in the request
expect(body.message).to.contain('id');
}));

it.skip('should not allow read only properties in requests (deep nested schema $refs)', async () =>
request(app)
.post(`${app.basePath}/products/nested`)
.set('content-type', 'application/json')
.send({
name: 'some name',
price: 10.99,
reviews: [
{
id: 10,
rating: 5,
},
],
})
.expect(400)
.then(r => {
const body = r.body;
console.log(body);
Expand Down
83 changes: 45 additions & 38 deletions test/resources/read.only.yaml
Expand Up @@ -72,20 +72,23 @@ paths:
schema:
$ref: '#/components/schemas/Product'

# TODO add nested test
# /products/nested:
# get:
# description: get products defined inline
# operationId: getProductsNested
# responses:
# '200':
# description: pet response
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: '#/components/schemas/ProductNested'
/products/nested:
post:
description: create products
operationId: createProductsNested
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductNested'
responses:
'200':
description: pet response
content:
application/json:
schema:
$ref: '#/components/schemas/ProductNested'


components:
Expand All @@ -105,28 +108,32 @@ components:
format: date-time
readOnly: true

# TODO add nested test
# ProductNested:
# type: object
# properties:
# id:
# type: string
# readOnly: true
# name:
# type: string
# price:
# type: number
# created_at:
# type: string
# format: date-time
# readOnly: true
# reviews:
# type: array
# items:
# $ref: '#/components/schemas/Review'
# TODO add nested test
ProductNested:
type: object
properties:
id:
type: string
readOnly: true
name:
type: string
price:
type: number
created_at:
type: string
format: date-time
readOnly: true
reviews:
type: array
items:
$ref: '#/components/schemas/Review'

# Review:
# type: object
# properties:
# rating:
# type: integer
Review:
type: object
properties:
id:
type: integer
readOnly: true
rating:
type: integer

0 comments on commit 5694276

Please sign in to comment.