Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: SQL Injection Prevention (Flow Simulation) #23

Merged
merged 3 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions packages/core/src/lib/data-query/builder/dataQueryBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { DataSource, Pagination } from '@vulcan-sql/core/models';
import {
DataSource,
PreparedQueryParams,
Pagination,
BindParameters,
} from '@vulcan-sql/core/models';
import * as uuid from 'uuid';

import { find, isEmpty } from 'lodash';
import {
Expand Down Expand Up @@ -180,7 +186,9 @@ export interface IDataQueryBuilder {
readonly statement: string;
readonly operations: SQLClauseOperation;
readonly dataSource: DataSource;

readonly bindParams: BindParameters;
// used for distinguish different builder
readonly identifier: string;
// Select clause methods
select(...columns: Array<SelectedColumn | string>): IDataQueryBuilder;
distinct(...columns: Array<SelectedColumn | string>): IDataQueryBuilder;
Expand Down Expand Up @@ -405,18 +413,24 @@ export class DataQueryBuilder implements IDataQueryBuilder {
// record all operations for different SQL clauses
public readonly operations: SQLClauseOperation;
public readonly dataSource: DataSource;
public readonly bindParams: BindParameters;
public pagination?: Pagination;
public readonly identifier: string;
constructor({
statement,
operations,
bindParams,
dataSource,
}: {
statement: string;
operations?: SQLClauseOperation;
bindParams: BindParameters;
dataSource: DataSource;
}) {
this.identifier = uuid.v4();
this.statement = statement;
this.dataSource = dataSource;
this.bindParams = bindParams;
this.operations = operations || {
select: null,
where: [],
Expand Down Expand Up @@ -618,6 +632,7 @@ export class DataQueryBuilder implements IDataQueryBuilder {
const wrappedBuilder = new DataQueryBuilder({
statement: '',
dataSource: this.dataSource,
bindParams: this.bindParams,
});
builderCallback(wrappedBuilder);
this.recordWhere({
Expand Down Expand Up @@ -1065,6 +1080,7 @@ export class DataQueryBuilder implements IDataQueryBuilder {
statement: this.statement,
dataSource: this.dataSource,
operations: this.operations,
bindParams: this.bindParams,
});
}

Expand All @@ -1078,6 +1094,7 @@ export class DataQueryBuilder implements IDataQueryBuilder {
const result = await this.dataSource.execute({
statement: this.statement,
operations: this.operations,
bindParams: this.bindParams,
pagination: this.pagination,
});

Expand Down
11 changes: 7 additions & 4 deletions packages/core/src/lib/data-query/executor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { DataSource } from '@vulcan-sql/core';
import { BindParameters, DataSource } from '@vulcan-sql/core/models';
import { inject, injectable } from 'inversify';
import { TYPES } from '@vulcan-sql/core/types';
import { DataQueryBuilder, IDataQueryBuilder } from './builder';

export interface IExecutor {
createBuilder(query: string): Promise<IDataQueryBuilder>;
createBuilder(
query: string,
bindParams: BindParameters
): Promise<IDataQueryBuilder>;
}

@injectable()
Expand All @@ -15,12 +18,12 @@ export class QueryExecutor implements IExecutor {
}
/**
* create data query builder
* @param query the sql statement for query
* @returns
*/
public async createBuilder(query: string) {
public async createBuilder(query: string, bindParams: BindParameters) {
return new DataQueryBuilder({
statement: query,
bindParams,
dataSource: this.dataSource,
});
}
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/lib/data-source/pg.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Stream } from 'stream';
import {
BindParameters,
DataResult,
DataSource,
ExecuteOptions,
IdentifierParameters,
RequestParameters,
VulcanExtensionId,
VulcanInternalExtension,
} from '../../models/extensions';
Expand All @@ -20,4 +23,19 @@ export class PGDataSource extends DataSource {
},
};
}
public async prepare(params: RequestParameters) {
const identifiers = {} as IdentifierParameters;
const binds = {} as BindParameters;
let index = 1;
for (const key of Object.keys(params)) {
const identifier = `$${index}`;
identifiers[key] = identifier;
binds[identifier] = params[key];
index += 1;
}
return {
identifiers,
binds,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export class ReqTagRunner extends TagRunner {
.split(/\r?\n/)
.filter((line) => line.trim().length > 0)
.join('\n');
const builder = await this.executor.createBuilder(query);
// Get bind real parameters and pass to data query builder for data source used.
const binds = (context.ctx || {})['_paramBinds'] || {};
const builder = await this.executor.createBuilder(query, binds);
context.setVariable(name, builder);

if (args[1] === 'true') {
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/lib/template-engine/templateEngine.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Compiler, TemplateMetadata } from './compiler';
import { injectable, inject, optional } from 'inversify';
import { TYPES } from '@vulcan-sql/core/types';
import { TemplateEngineOptions } from '../../options';
import {
CodeLoader,
Pagination,
PreparedQueryParams,
TemplateProvider,
} from '@vulcan-sql/core/models';
import { get, omit } from 'lodash';

export type AllTemplateMetadata = Record<string, TemplateMetadata>;

Expand Down Expand Up @@ -65,6 +66,19 @@ export class TemplateEngine {
data: T,
pagination?: Pagination
): Promise<any> {
return this.compiler.execute(templateName, data, pagination);
const others = omit(data, '_prepared');
const prepared: PreparedQueryParams | undefined = get(data, '_prepared');
// wrap to context object
return this.compiler.execute(
templateName,
{
context: {
...others,
['params']: prepared?.identifiers || {},
},
['_paramBinds']: prepared?.binds || {},
},
pagination
);
}
}
26 changes: 26 additions & 0 deletions packages/core/src/models/extensions/dataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ import { Stream } from 'stream';
import { ExtensionBase } from './base';
import { VulcanExtension } from './decorators';

// Original request parameters
export interface RequestParameters {
[name: string]: any;
}

export type BindParameters = {
// the value is real param data
[identifier: string]: string;
};

export type IdentifierParameters = {
// the value is identifier
[paramName: string]: string;
};

// prepared query parameters for providing data source to prevent sql
export interface PreparedQueryParams {
// e.g: params['members'] = '@members'
identifiers: IdentifierParameters;
// e.g: binds['@members'] = '17'
binds: BindParameters;
}

export type DataColumn = { name: string; type: string };

export interface DataResult {
Expand All @@ -15,10 +38,13 @@ export interface DataResult {
export interface ExecuteOptions {
statement: string;
operations: SQLClauseOperation;
bindParams: BindParameters;
pagination?: Pagination;
}

@VulcanExtension(TYPES.Extension_DataSource, { enforcedId: true })
export abstract class DataSource extends ExtensionBase {
abstract execute(options: ExecuteOptions): Promise<DataResult>;
// prepare parameterized format for query in the later
abstract prepare(params: RequestParameters): Promise<PreparedQueryParams>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {
GroupByClauseOperations,
DataQueryBuilder,
} from '@vulcan-sql/core/data-query';
import { DataSource } from '@vulcan-sql/core/models';
import { DataSource, BindParameters } from '@vulcan-sql/core/models';

describe('Test data query builder > group by clause', () => {
let stubDataSource: sinon.StubbedInstance<DataSource>;
let stubBindParameters: sinon.StubbedInstance<BindParameters>;

beforeEach(() => {
stubDataSource = sinon.stubInterface<DataSource>();
stubBindParameters = sinon.stubInterface<BindParameters>();
});

it.each([
Expand All @@ -30,6 +32,7 @@ describe('Test data query builder > group by clause', () => {
let builder = new DataQueryBuilder({
statement: 'select * from orders',
dataSource: stubDataSource,
bindParams: stubBindParameters,
});
columns.map((column) => {
builder = builder.groupBy(column);
Expand All @@ -55,6 +58,7 @@ describe('Test data query builder > group by clause', () => {
const builder = new DataQueryBuilder({
statement: 'select * from orders',
dataSource: stubDataSource,
bindParams: stubBindParameters,
});
builder.groupBy(first, second, third);

Expand Down
Loading