From 145b1199b79f5dc33893bca32e110ec2d7700985 Mon Sep 17 00:00:00 2001 From: tada5hi Date: Fri, 28 Oct 2022 16:17:51 +0200 Subject: [PATCH] fix: allow non matching regex strings, if permitted by options --- src/parameter/fields/parse.ts | 48 ++++++++++++++------------ src/parameter/fields/utils/index.ts | 1 + src/parameter/fields/utils/name.ts | 10 ++++++ src/parameter/filters/parse.ts | 8 +++++ src/parameter/relations/parse.ts | 4 ++- src/parameter/relations/utils/index.ts | 1 + src/parameter/relations/utils/path.ts | 10 ++++++ src/parameter/sort/parse.ts | 9 +++++ test/unit/fields.spec.ts | 8 +++++ test/unit/filters.spec.ts | 12 +++++++ test/unit/relations.spec.ts | 10 ++++++ test/unit/sort.spec.ts | 11 +++++- 12 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 src/parameter/fields/utils/name.ts create mode 100644 src/parameter/relations/utils/path.ts diff --git a/src/parameter/fields/parse.ts b/src/parameter/fields/parse.ts index 7c6f3a6a..fe229f54 100644 --- a/src/parameter/fields/parse.ts +++ b/src/parameter/fields/parse.ts @@ -15,6 +15,7 @@ import { FieldsInputTransformed, FieldsParseOptions, FieldsParseOutput, } from './type'; import { + isValidFieldName, parseFieldsInput, removeFieldInputOperator, transformFieldsInput, } from './utils'; @@ -94,11 +95,11 @@ export function parseQueryFields( const output : FieldsParseOutput = []; for (let i = 0; i < keys.length; i++) { - const key = keys[i]; + const path = keys[i]; if ( - !isFieldPathAllowedByRelations({ path: key }, options.relations) && - key !== DEFAULT_ID + !isFieldPathAllowedByRelations({ path }, options.relations) && + path !== DEFAULT_ID ) { continue; } @@ -106,14 +107,14 @@ export function parseQueryFields( let fields : string[] = []; if ( - hasOwnProperty(data, key) + hasOwnProperty(data, path) ) { - fields = parseFieldsInput(data[key]); + fields = parseFieldsInput(data[path]); } else if ( - hasOwnProperty(reverseMapping, key) + hasOwnProperty(reverseMapping, path) ) { - if (hasOwnProperty(data, reverseMapping[key])) { - fields = parseFieldsInput(data[reverseMapping[key]]); + if (hasOwnProperty(data, reverseMapping[path])) { + fields = parseFieldsInput(data[reverseMapping[path]]); } } @@ -126,17 +127,18 @@ export function parseQueryFields( if (fields.length > 0) { for (let j = 0; j < fields.length; j++) { fields[j] = applyMapping( - buildFieldWithPath({ name: fields[j], path: key }), + buildFieldWithPath({ name: fields[j], path }), options.mapping, true, ); } - if (hasOwnProperty(domainFields, key)) { - fields = fields - .filter((field) => domainFields[key].indexOf( - removeFieldInputOperator(field), - ) !== -1); + if (hasOwnProperty(domainFields, path)) { + fields = fields.filter((field) => domainFields[path].indexOf( + removeFieldInputOperator(field), + ) !== -1); + } else { + fields = fields.filter((field) => isValidFieldName(removeFieldInputOperator(field))); } transformed = transformFieldsInput( @@ -146,17 +148,17 @@ export function parseQueryFields( if ( transformed.default.length === 0 && - hasOwnProperty(defaultDomainFields, key) + hasOwnProperty(defaultDomainFields, path) ) { - transformed.default = defaultDomainFields[key]; + transformed.default = defaultDomainFields[path]; } if ( transformed.included.length === 0 && transformed.default.length === 0 && - hasOwnProperty(allowedDomainFields, key) + hasOwnProperty(allowedDomainFields, path) ) { - transformed.default = allowedDomainFields[key]; + transformed.default = allowedDomainFields[path]; } transformed.default = Array.from(new Set([ @@ -173,16 +175,16 @@ export function parseQueryFields( if (transformed.default.length > 0) { for (let j = 0; j < transformed.default.length; j++) { - let path : string | undefined; - if (key !== DEFAULT_ID) { - path = key; + let destPath : string | undefined; + if (path !== DEFAULT_ID) { + destPath = path; } else if (options.defaultPath) { - path = options.defaultPath; + destPath = options.defaultPath; } output.push({ key: transformed.default[j], - ...(path ? { path } : {}), + ...(destPath ? { path: destPath } : {}), }); } } diff --git a/src/parameter/fields/utils/index.ts b/src/parameter/fields/utils/index.ts index 08daee26..ef1dd324 100644 --- a/src/parameter/fields/utils/index.ts +++ b/src/parameter/fields/utils/index.ts @@ -7,3 +7,4 @@ export * from './domain'; export * from './input'; +export * from './name'; diff --git a/src/parameter/fields/utils/name.ts b/src/parameter/fields/utils/name.ts new file mode 100644 index 00000000..ca396b78 --- /dev/null +++ b/src/parameter/fields/utils/name.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2022. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +export function isValidFieldName(input: string) : boolean { + return /^[a-zA-Z_][a-zA-Z0-9_]*$/gu.test(input); +} diff --git a/src/parameter/filters/parse.ts b/src/parameter/filters/parse.ts index bb64fb89..86adf143 100644 --- a/src/parameter/filters/parse.ts +++ b/src/parameter/filters/parse.ts @@ -14,6 +14,7 @@ import { getFieldDetails, hasOwnProperty, isFieldNonRelational, isFieldPathAllowedByRelations, } from '../../utils'; +import { isValidFieldName } from '../fields'; import { ParseAllowedOption } from '../type'; import { flattenParseAllowedOption, isPathCoveredByParseAllowedOption } from '../utils'; import { FilterComparisonOperator } from './constants'; @@ -164,6 +165,13 @@ export function parseQueryFilters( const fieldDetails : FieldDetails = getFieldDetails(keys[i]); + if ( + typeof options.allowed === 'undefined' && + !isValidFieldName(fieldDetails.name) + ) { + continue; + } + if ( !isFieldPathAllowedByRelations(fieldDetails, options.relations) && !isFieldNonRelational(fieldDetails) diff --git a/src/parameter/relations/parse.ts b/src/parameter/relations/parse.ts index 791df13e..01856eb3 100644 --- a/src/parameter/relations/parse.ts +++ b/src/parameter/relations/parse.ts @@ -11,7 +11,7 @@ import { applyMapping, hasOwnProperty } from '../../utils'; import { isPathCoveredByParseAllowedOption } from '../utils'; import { RelationsParseOptions, RelationsParseOutput } from './type'; -import { includeParents } from './utils'; +import { includeParents, isValidRelationPath } from './utils'; // -------------------------------------------------- @@ -64,6 +64,8 @@ export function parseQueryRelations( if (options.allowed) { items = items.filter((item) => isPathCoveredByParseAllowedOption(options.allowed, item)); + } else { + items = items.filter((item) => isValidRelationPath(item)); } if (options.includeParents) { diff --git a/src/parameter/relations/utils/index.ts b/src/parameter/relations/utils/index.ts index 6e6c8972..6f89c44f 100644 --- a/src/parameter/relations/utils/index.ts +++ b/src/parameter/relations/utils/index.ts @@ -6,3 +6,4 @@ */ export * from './parents'; +export * from './path'; diff --git a/src/parameter/relations/utils/path.ts b/src/parameter/relations/utils/path.ts new file mode 100644 index 00000000..f5288089 --- /dev/null +++ b/src/parameter/relations/utils/path.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2022. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +export function isValidRelationPath(input: string) : boolean { + return /^[a-zA-Z0-9_-]+([.]*[a-zA-Z0-9_-])*$/gu.test(input); +} diff --git a/src/parameter/sort/parse.ts b/src/parameter/sort/parse.ts index 0243fb0d..cc838557 100644 --- a/src/parameter/sort/parse.ts +++ b/src/parameter/sort/parse.ts @@ -13,6 +13,7 @@ import { hasOwnProperty, isFieldNonRelational, isFieldPathAllowedByRelations, } from '../../utils'; +import { isValidFieldName } from '../fields'; import { ParseAllowedOption } from '../type'; import { flattenParseAllowedOption, isPathCoveredByParseAllowedOption } from '../utils'; @@ -146,6 +147,14 @@ export function parseQuerySort( const key: string = applyMapping(parts[i], options.mapping); const fieldDetails = getFieldDetails(key); + + if ( + typeof options.allowed === 'undefined' && + !isValidFieldName(fieldDetails.name) + ) { + continue; + } + if ( !isFieldPathAllowedByRelations(fieldDetails, options.relations) && !isFieldNonRelational(fieldDetails) diff --git a/test/unit/fields.spec.ts b/test/unit/fields.spec.ts index 8db82125..0429dbb5 100644 --- a/test/unit/fields.spec.ts +++ b/test/unit/fields.spec.ts @@ -147,6 +147,14 @@ describe('src/fields/index.ts', () => { data = parseQueryFields(['id']); expect(data).toEqual([{ key: 'id' }] as FieldsParseOutput); + // invalid field names + data = parseQueryFields(['"id', 'name!']); + expect(data).toEqual([] as FieldsParseOutput); + + // ignore field name pattern, if permitted by allowed key + data = parseQueryFields(['name!'], { allowed: ['name!'] }); + expect(data).toEqual([{key: 'name!'}] as FieldsParseOutput); + // empty allowed -> allows nothing data = parseQueryFields(['id'], {allowed: []}); expect(data).toEqual([] as FieldsParseOutput); diff --git a/test/unit/filters.spec.ts b/test/unit/filters.spec.ts index 81ca79f6..9e872af0 100644 --- a/test/unit/filters.spec.ts +++ b/test/unit/filters.spec.ts @@ -41,6 +41,18 @@ describe('src/filter/index.ts', () => { operator: FilterComparisonOperator.EQUAL, }] as FiltersParseOutput); + // invalid field name + allowedFilter = parseQueryFilters({ 'id!': 1 }, { allowed: undefined }); + expect(allowedFilter).toEqual([] as FiltersParseOutput); + + // ignore field name pattern, if permitted by allowed key + allowedFilter = parseQueryFilters({ 'id!': 1 }, { allowed: ['id!'] }); + expect(allowedFilter).toEqual([{ + key: 'id!', + value: 1, + operator: FilterComparisonOperator.EQUAL, + }] as FiltersParseOutput); + allowedFilter = parseQueryFilters({ name: 'tada5hi' }, { default: { name: 'admin' } }); expect(allowedFilter).toEqual([{ key: 'name', diff --git a/test/unit/relations.spec.ts b/test/unit/relations.spec.ts index adc99740..756525f2 100644 --- a/test/unit/relations.spec.ts +++ b/test/unit/relations.spec.ts @@ -18,6 +18,16 @@ describe('src/relations/index.ts', () => { allowed = parseQueryRelations([], { allowed: ['profile'] }); expect(allowed).toEqual([]); + // invalid path + allowed = parseQueryRelations(['profile!']); + expect(allowed).toEqual([]); + + // ignore path pattern, if permitted by allowed key + allowed = parseQueryRelations(['profile!'], {allowed: ['profile!']}); + expect(allowed).toEqual([ + { key: 'profile!', value: 'profile!'} + ] as RelationsParseOutput); + // with alias allowed = parseQueryRelations('pro', { mapping: { pro: 'profile' }, allowed: ['profile'] }); expect(allowed).toEqual([ diff --git a/test/unit/sort.spec.ts b/test/unit/sort.spec.ts index dc6b922b..09c17d4a 100644 --- a/test/unit/sort.spec.ts +++ b/test/unit/sort.spec.ts @@ -11,11 +11,12 @@ import { SortParseOutput, parseQueryRelations, parseQuerySort, + FieldsParseOutput, } from '../../src'; import {User} from "../data"; describe('src/sort/index.ts', () => { - it('should transform sort data', () => { + it('should parse sort data', () => { // sort asc let transformed = parseQuerySort('id', { allowed: ['id'] }); expect(transformed).toEqual([{ key: 'id', value: SortDirection.ASC }] as SortParseOutput); @@ -24,6 +25,14 @@ describe('src/sort/index.ts', () => { transformed = parseQuerySort('-id', { allowed: ['id'] }); expect(transformed).toEqual([{ key: 'id', value: SortDirection.DESC }] as SortParseOutput); + // invalid field names + transformed = parseQuerySort('-!id'); + expect(transformed).toEqual([] as FieldsParseOutput); + + // ignore field name pattern, if permitted by allowed key + transformed = parseQuerySort(['-!id'], { allowed: ['!id'] }); + expect(transformed).toEqual([{key: '!id', value: SortDirection.DESC}] as FieldsParseOutput); + // empty allowed transformed = parseQuerySort('-id', { allowed: [] }); expect(transformed).toEqual([] as SortParseOutput);