mirror of
https://github.com/strapi/strapi.git
synced 2025-10-29 08:59:34 +00:00
Fix input payload validation
This commit is contained in:
parent
023e95b482
commit
90a86f595c
89
api-tests/core/strapi/api/validate-body.test.api.js
Normal file
89
api-tests/core/strapi/api/validate-body.test.api.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { createStrapiInstance } = require('api-tests/strapi');
|
||||||
|
const { createTestBuilder } = require('api-tests/builder');
|
||||||
|
const { createContentAPIRequest } = require('api-tests/request');
|
||||||
|
|
||||||
|
const builder = createTestBuilder();
|
||||||
|
let strapi;
|
||||||
|
let rq;
|
||||||
|
let data;
|
||||||
|
|
||||||
|
const productFixtures = [
|
||||||
|
{
|
||||||
|
name: 'foo',
|
||||||
|
description: 'first product',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bar',
|
||||||
|
description: 'second product',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const product = {
|
||||||
|
attributes: {
|
||||||
|
name: { type: 'string' },
|
||||||
|
description: { type: 'text' },
|
||||||
|
},
|
||||||
|
displayName: 'product',
|
||||||
|
singularName: 'product',
|
||||||
|
pluralName: 'products',
|
||||||
|
description: '',
|
||||||
|
collectionName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Validate Body', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await builder
|
||||||
|
.addContentType(product)
|
||||||
|
.addFixtures(product.singularName, productFixtures)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
data = builder.fixturesFor(product.singularName);
|
||||||
|
|
||||||
|
strapi = await createStrapiInstance();
|
||||||
|
rq = await createContentAPIRequest({ strapi });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await strapi.destroy();
|
||||||
|
await builder.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Create', () => {
|
||||||
|
test('Cannot specify the ID during entity creation', async () => {
|
||||||
|
const createPayload = { data: { id: -1, name: 'baz', description: 'third product' } };
|
||||||
|
|
||||||
|
const response = await rq.post('/products', { body: createPayload });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { id, attributes } = response.body.data;
|
||||||
|
|
||||||
|
expect(id).not.toBe(createPayload.data.id);
|
||||||
|
|
||||||
|
expect(attributes).toHaveProperty('name', createPayload.data.name);
|
||||||
|
expect(attributes).toHaveProperty('description', createPayload.data.description);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Update', () => {
|
||||||
|
test('ID cannot be updated, but allowed fields can', async () => {
|
||||||
|
const target = data[0];
|
||||||
|
|
||||||
|
const updatePayload = { data: { id: -1, name: 'baz' } };
|
||||||
|
|
||||||
|
const response = await rq.put(`/products/${target.id}`, {
|
||||||
|
body: updatePayload,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { id, attributes } = response.body.data;
|
||||||
|
|
||||||
|
expect(id).toBe(target.id);
|
||||||
|
expect(attributes).toHaveProperty('name', updatePayload.data.name);
|
||||||
|
expect(attributes).toHaveProperty('description', target.description);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -241,12 +241,7 @@ export default ({ action, ability, model }: any) => {
|
|||||||
|
|
||||||
const nonVisibleWritableAttributes = intersection(nonVisibleAttributes, writableAttributes);
|
const nonVisibleWritableAttributes = intersection(nonVisibleAttributes, writableAttributes);
|
||||||
|
|
||||||
return uniq([
|
return uniq([...fields, ...COMPONENT_FIELDS, ...nonVisibleWritableAttributes]);
|
||||||
...fields,
|
|
||||||
...STATIC_FIELDS,
|
|
||||||
...COMPONENT_FIELDS,
|
|
||||||
...nonVisibleWritableAttributes,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOutputFields = (fields = []) => {
|
const getOutputFields = (fields = []) => {
|
||||||
|
|||||||
@ -118,7 +118,7 @@ export default ({ action, ability, model }: any) => {
|
|||||||
const wrapValidate = (createValidateFunction: any) => {
|
const wrapValidate = (createValidateFunction: any) => {
|
||||||
// TODO
|
// TODO
|
||||||
// @ts-expect-error define the correct return type
|
// @ts-expect-error define the correct return type
|
||||||
const wrappedValidate = async (data, options = {}) => {
|
const wrappedValidate = async (data, options = {}): Promise<unknown> => {
|
||||||
if (isArray(data)) {
|
if (isArray(data)) {
|
||||||
return Promise.all(data.map((entity: unknown) => wrappedValidate(entity, options)));
|
return Promise.all(data.map((entity: unknown) => wrappedValidate(entity, options)));
|
||||||
}
|
}
|
||||||
@ -187,12 +187,7 @@ export default ({ action, ability, model }: any) => {
|
|||||||
|
|
||||||
const nonVisibleWritableAttributes = intersection(nonVisibleAttributes, writableAttributes);
|
const nonVisibleWritableAttributes = intersection(nonVisibleAttributes, writableAttributes);
|
||||||
|
|
||||||
return uniq([
|
return uniq([...fields, ...COMPONENT_FIELDS, ...nonVisibleWritableAttributes]);
|
||||||
...fields,
|
|
||||||
...STATIC_FIELDS,
|
|
||||||
...COMPONENT_FIELDS,
|
|
||||||
...nonVisibleWritableAttributes,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getQueryFields = (fields = []) => {
|
const getQueryFields = (fields = []) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { CurriedFunction1 } from 'lodash';
|
import { CurriedFunction1 } from 'lodash';
|
||||||
import { isArray, cloneDeep } from 'lodash/fp';
|
import { isArray, cloneDeep, omit } from 'lodash/fp';
|
||||||
|
|
||||||
import { getNonWritableAttributes } from '../content-types';
|
import { getNonWritableAttributes } from '../content-types';
|
||||||
import { pipeAsync } from '../async';
|
import { pipeAsync } from '../async';
|
||||||
@ -34,7 +34,9 @@ const createContentAPISanitizers = () => {
|
|||||||
const nonWritableAttributes = getNonWritableAttributes(schema);
|
const nonWritableAttributes = getNonWritableAttributes(schema);
|
||||||
|
|
||||||
const transforms = [
|
const transforms = [
|
||||||
// Remove non writable attributes
|
// Remove first level ID in inputs
|
||||||
|
omit('id'),
|
||||||
|
// Remove non-writable attributes
|
||||||
traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema }),
|
traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema }),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { CurriedFunction1 } from 'lodash';
|
import { CurriedFunction1 } from 'lodash';
|
||||||
import { isArray } from 'lodash/fp';
|
import { isArray, isObject } from 'lodash/fp';
|
||||||
|
|
||||||
import { getNonWritableAttributes } from '../content-types';
|
import { getNonWritableAttributes } from '../content-types';
|
||||||
import { pipeAsync } from '../async';
|
import { pipeAsync } from '../async';
|
||||||
|
import { throwInvalidParam } from './utils';
|
||||||
|
|
||||||
import * as visitors from './visitors';
|
import * as visitors from './visitors';
|
||||||
import * as validators from './validators';
|
import * as validators from './validators';
|
||||||
@ -37,7 +38,12 @@ const createContentAPIValidators = () => {
|
|||||||
const nonWritableAttributes = getNonWritableAttributes(schema);
|
const nonWritableAttributes = getNonWritableAttributes(schema);
|
||||||
|
|
||||||
const transforms = [
|
const transforms = [
|
||||||
// non writable attributes
|
(data: unknown) => {
|
||||||
|
if (isObject(data) && 'id' in data) {
|
||||||
|
throwInvalidParam({ key: 'id' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// non-writable attributes
|
||||||
traverseEntity(visitors.throwRestrictedFields(nonWritableAttributes), { schema }),
|
traverseEntity(visitors.throwRestrictedFields(nonWritableAttributes), { schema }),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user