From 1867658cde24065cc3c057ce8d0099d8109ba8c9 Mon Sep 17 00:00:00 2001 From: sabir_hassan Date: Thu, 3 Jun 2021 15:08:48 +0500 Subject: [PATCH] fix: add validations for table name and table structure #276 --- sasjs-tests/src/testSuites/RequestData.ts | 52 +++++++++- src/SASjs.ts | 117 +++++++++++++++++----- 2 files changed, 143 insertions(+), 26 deletions(-) diff --git a/sasjs-tests/src/testSuites/RequestData.ts b/sasjs-tests/src/testSuites/RequestData.ts index a2088f3..b4bf93e 100644 --- a/sasjs-tests/src/testSuites/RequestData.ts +++ b/sasjs-tests/src/testSuites/RequestData.ts @@ -176,11 +176,59 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({ name: 'sendObj', tests: [ { - title: 'Invalid column name', + title: 'Table name starts with numeric', description: 'Should throw an error', test: async () => { const invalidData: any = { - '1 invalid table': [{ col1: 42 }] + '1InvalidTable': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name contains a space', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + 'an invalidTable': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name contains a special character', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + 'anInvalidTable#': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name exceeds max length of 32 characters', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: "Invalid data object's structure", + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + inData: [[{ data: 'value' }]] } return adapter.request('common/sendObj', invalidData).catch((e) => e) }, diff --git a/src/SASjs.ts b/src/SASjs.ts index 4d9c71c..4c3cfb6 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -553,37 +553,106 @@ export default class SASjs { ...config } - if (config.serverType === ServerType.SasViya && config.contextName) { - if (config.useComputeApi) { - return await this.computeJobExecutor!.execute( - sasJob, - data, - config, - loginRequiredCallback, - accessToken - ) + const validationResult = this.validateInput(data) + + if (validationResult.status) { + if (config.serverType === ServerType.SasViya && config.contextName) { + if (config.useComputeApi) { + return await this.computeJobExecutor!.execute( + sasJob, + data, + config, + loginRequiredCallback, + accessToken + ) + } else { + return await this.jesJobExecutor!.execute( + sasJob, + data, + config, + loginRequiredCallback, + accessToken + ) + } + } else if ( + config.serverType === ServerType.Sas9 && + config.username && + config.password + ) { + return await this.sas9JobExecutor!.execute(sasJob, data, config) } else { - return await this.jesJobExecutor!.execute( + return await this.webJobExecutor!.execute( sasJob, data, config, - loginRequiredCallback, - accessToken + loginRequiredCallback ) } - } else if ( - config.serverType === ServerType.Sas9 && - config.username && - config.password - ) { - return await this.sas9JobExecutor!.execute(sasJob, data, config) } else { - return await this.webJobExecutor!.execute( - sasJob, - data, - config, - loginRequiredCallback - ) + return Promise.reject(new ErrorResponse(validationResult.msg)) + } + } + + /** + * this function validates the structure of input data and verify that proper rules are being followed for table name + * + * @param data a json object contains one or more table, it can also be null + * @returns a object which contains two attributes: 1) status: boolean, 2) msg: string + */ + private validateInput(data: { [key: string]: any }): { + status: boolean + msg: string + } { + if (data === null) return { status: true, msg: '' } + for (const key in data) { + if (key.match(/^[a-zA-Z_]/)) { + if (key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) { + if (key.length <= 32) { + if (this.getType(data[key]) === 'Array') { + for (let i = 0; i < data[key].length; i++) { + if (this.getType(data[key][i]) !== 'object') { + return { + status: false, + msg: `Table ${key} contains invalid structure.` + } + } + } + } else { + return { + status: false, + msg: 'Parameter data contains invalid table structure.' + } + } + } else { + return { + status: false, + msg: 'Maximum length for table name could be 32 characters.' + } + } + } else { + return { status: false, msg: 'Table name should be alphanumeric.' } + } + } else { + return { + status: false, + msg: 'First letter of table should be alphabet or underscore.' + } + } + } + return { status: true, msg: '' } + } + + /** + * this function returns the type of variable + * + * @param data it could be anything, like string, array, object etc. + * @returns a string which tells the type of input parameter + */ + private getType(data: any): string { + if (Array.isArray(data)) { + return 'Array' + } else { + return typeof data } }