Skip to content

Commit 777a035

Browse files
committed
Silence warnings using accept_potentially_dangerous_queries: true
1 parent 8820b34 commit 777a035

File tree

9 files changed

+98
-19
lines changed

9 files changed

+98
-19
lines changed

packages/sync-rules/src/SqlBucketDescriptor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
EvaluatedParametersResult,
1111
EvaluationResult,
1212
QueryBucketIdOptions,
13+
QueryParseOptions,
1314
RequestParameters,
1415
SourceSchema,
1516
SqliteRow
@@ -57,8 +58,8 @@ export class SqlBucketDescriptor {
5758
};
5859
}
5960

60-
addParameterQuery(sql: string, schema: SourceSchema | undefined): QueryParseResult {
61-
const parameterQuery = SqlParameterQuery.fromSql(this.name, sql, schema);
61+
addParameterQuery(sql: string, schema: SourceSchema | undefined, options: QueryParseOptions): QueryParseResult {
62+
const parameterQuery = SqlParameterQuery.fromSql(this.name, sql, schema, options);
6263
if (this.bucket_parameters == null) {
6364
this.bucket_parameters = parameterQuery.bucket_parameters;
6465
} else {

packages/sync-rules/src/SqlParameterQuery.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ParameterMatchClause,
1414
ParameterValueClause,
1515
QueryBucketIdOptions,
16+
QueryParseOptions,
1617
QuerySchema,
1718
RequestParameters,
1819
RowValueClause,
@@ -33,7 +34,8 @@ export class SqlParameterQuery {
3334
static fromSql(
3435
descriptor_name: string,
3536
sql: string,
36-
schema?: SourceSchema
37+
schema?: SourceSchema,
38+
options?: QueryParseOptions
3739
): SqlParameterQuery | StaticSqlParameterQuery {
3840
const parsed = parse(sql, { locationTracking: true });
3941
const rows = new SqlParameterQuery();
@@ -49,7 +51,7 @@ export class SqlParameterQuery {
4951

5052
if (q.from == null) {
5153
// E.g. SELECT token_parameters.user_id as user_id WHERE token_parameters.is_admin
52-
return StaticSqlParameterQuery.fromSql(descriptor_name, sql, q);
54+
return StaticSqlParameterQuery.fromSql(descriptor_name, sql, q, options);
5355
}
5456

5557
rows.errors.push(...checkUnsupportedFeatures(sql, q));
@@ -137,8 +139,10 @@ export class SqlParameterQuery {
137139
rows.tools = tools;
138140
rows.errors.push(...tools.errors);
139141

140-
if (rows.usesDangerousRequestParameters) {
141-
rows.errors.push(new SqlRuleError('Pontially dangerous query based on unauthenticated client parameters', sql));
142+
if (rows.usesDangerousRequestParameters && !options?.accept_potentially_dangerous_queries) {
143+
let err = new SqlRuleError('Pontially dangerous query based on unauthenticated client parameters', sql);
144+
err.type = 'warning';
145+
rows.errors.push(err);
142146
}
143147
return rows;
144148
}

packages/sync-rules/src/SqlSyncRules.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import {
1616
isEvaluatedRow,
1717
isEvaluationError,
1818
QueryBucketIdOptions,
19+
QueryParseOptions,
1920
RequestParameters,
2021
SourceSchema,
2122
SqliteRow,
2223
SyncRules
2324
} from './types.js';
2425

26+
const ACCEPT_POTENTIALLY_DANGEROUS_QUERIES = Symbol('ACCEPT_POTENTIALLY_DANGEROUS_QUERIES');
27+
2528
export class SqlSyncRules implements SyncRules {
2629
bucket_descriptors: SqlBucketDescriptor[] = [];
2730
idSequence = new IdSequence();
@@ -50,7 +53,19 @@ export class SqlSyncRules implements SyncRules {
5053
const schema = options?.schema;
5154

5255
const lineCounter = new LineCounter();
53-
const parsed = parseDocument(yaml, { schema: 'core', keepSourceTokens: true, lineCounter });
56+
const parsed = parseDocument(yaml, {
57+
schema: 'core',
58+
keepSourceTokens: true,
59+
lineCounter,
60+
customTags: [
61+
{
62+
tag: '!accept_potentially_dangerous_queries',
63+
resolve(_text: string, _onError: (error: string) => void) {
64+
return ACCEPT_POTENTIALLY_DANGEROUS_QUERIES;
65+
}
66+
}
67+
]
68+
});
5469

5570
const rules = new SqlSyncRules(yaml);
5671

@@ -81,23 +96,28 @@ export class SqlSyncRules implements SyncRules {
8196
const { key: keyScalar, value } = entry as { key: Scalar; value: YAMLMap };
8297
const key = keyScalar.toString();
8398

99+
const accept_potentially_dangerous_queries =
100+
value.get('accept_potentially_dangerous_queries', true)?.value == true;
101+
const options: QueryParseOptions = {
102+
accept_potentially_dangerous_queries
103+
};
84104
const parameters = value.get('parameters', true) as unknown;
85105
const dataQueries = value.get('data', true) as unknown;
86106

87107
const descriptor = new SqlBucketDescriptor(key, rules.idSequence);
88108

89109
if (parameters instanceof Scalar) {
90110
rules.withScalar(parameters, (q) => {
91-
return descriptor.addParameterQuery(q, schema);
111+
return descriptor.addParameterQuery(q, schema, options);
92112
});
93113
} else if (parameters instanceof YAMLSeq) {
94114
for (let item of parameters.items) {
95115
rules.withScalar(item, (q) => {
96-
return descriptor.addParameterQuery(q, schema);
116+
return descriptor.addParameterQuery(q, schema, options);
97117
});
98118
}
99119
} else {
100-
descriptor.addParameterQuery('SELECT', schema);
120+
descriptor.addParameterQuery('SELECT', schema, options);
101121
}
102122

103123
if (!(dataQueries instanceof YAMLSeq)) {

packages/sync-rules/src/StaticSqlParameterQuery.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SelectedColumn, SelectFromStatement } from 'pgsql-ast-parser';
22
import { SqlRuleError } from './errors.js';
33
import { SqlTools } from './sql_filters.js';
44
import { checkUnsupportedFeatures, isClauseError, isParameterValueClause, sqliteBool } from './sql_support.js';
5-
import { ParameterValueClause, RequestParameters, SqliteJsonValue } from './types.js';
5+
import { ParameterValueClause, QueryParseOptions, RequestParameters, SqliteJsonValue } from './types.js';
66
import { getBucketId, isJsonValue } from './utils.js';
77

88
/**
@@ -12,7 +12,7 @@ import { getBucketId, isJsonValue } from './utils.js';
1212
* SELECT token_parameters.user_id as user_id WHERE token_parameters.is_admin
1313
*/
1414
export class StaticSqlParameterQuery {
15-
static fromSql(descriptor_name: string, sql: string, q: SelectFromStatement) {
15+
static fromSql(descriptor_name: string, sql: string, q: SelectFromStatement, options?: QueryParseOptions) {
1616
const query = new StaticSqlParameterQuery();
1717

1818
query.errors.push(...checkUnsupportedFeatures(sql, q));
@@ -50,8 +50,10 @@ export class StaticSqlParameterQuery {
5050

5151
query.errors.push(...tools.errors);
5252

53-
if (query.usesDangerousRequestParameters) {
54-
query.errors.push(new SqlRuleError('Pontially dangerous query based on unauthenticated client parameters', sql));
53+
if (query.usesDangerousRequestParameters && !options?.accept_potentially_dangerous_queries) {
54+
let err = new SqlRuleError('Pontially dangerous query based on unauthenticated client parameters', sql);
55+
err.type = 'warning';
56+
query.errors.push(err);
5557
}
5658
return query;
5759
}

packages/sync-rules/src/json_schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export const syncRulesSchema: ajvModule.Schema = {
1616
required: ['data'],
1717
examples: [{ data: ['select * from mytable'] }],
1818
properties: {
19+
accept_potentially_dangerous_queries: {
20+
description: 'If true, disables warnings on potentially dangerous queries',
21+
type: 'boolean'
22+
},
1923
parameters: {
2024
description: 'Parameter query(ies)',
2125
anyOf: [

packages/sync-rules/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export interface SyncRules {
1010
evaluateParameterRow(table: SourceTableInterface, row: SqliteRow): EvaluatedParametersResult[];
1111
}
1212

13+
export interface QueryParseOptions {
14+
accept_potentially_dangerous_queries?: boolean;
15+
}
16+
1317
export interface EvaluatedParameters {
1418
lookup: SqliteJsonValue[];
1519

packages/sync-rules/test/src/parameter_queries.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,9 @@ describe('parameter queries', () => {
449449

450450
test('request.parameters()', function () {
451451
const sql = "SELECT FROM posts WHERE category = request.parameters() ->> 'category_id'";
452-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
452+
const query = SqlParameterQuery.fromSql('mybucket', sql, undefined, {
453+
accept_potentially_dangerous_queries: true
454+
}) as SqlParameterQuery;
453455
expect(query.errors).toEqual([]);
454456
query.id = '1';
455457
expect(query.evaluateParameterRow({ id: 'group1', category: 'red' })).toEqual([
@@ -463,7 +465,9 @@ describe('parameter queries', () => {
463465

464466
test('nested request.parameters() (1)', function () {
465467
const sql = "SELECT FROM posts WHERE category = request.parameters() -> 'details' ->> 'category'";
466-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
468+
const query = SqlParameterQuery.fromSql('mybucket', sql, undefined, {
469+
accept_potentially_dangerous_queries: true
470+
}) as SqlParameterQuery;
467471
expect(query.errors).toEqual([]);
468472
query.id = '1';
469473
expect(query.getLookups(normalizeTokenParameters({}, { details: { category: 'red' } }))).toEqual([
@@ -473,7 +477,9 @@ describe('parameter queries', () => {
473477

474478
test('nested request.parameters() (2)', function () {
475479
const sql = "SELECT FROM posts WHERE category = request.parameters() ->> 'details.category'";
476-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
480+
const query = SqlParameterQuery.fromSql('mybucket', sql, undefined, {
481+
accept_potentially_dangerous_queries: true
482+
}) as SqlParameterQuery;
477483
expect(query.errors).toEqual([]);
478484
query.id = '1';
479485
expect(query.getLookups(normalizeTokenParameters({}, { details: { category: 'red' } }))).toEqual([
@@ -484,7 +490,9 @@ describe('parameter queries', () => {
484490
test('IN request.parameters()', function () {
485491
// Can use -> or ->> here
486492
const sql = "SELECT id as region_id FROM regions WHERE name IN request.parameters() -> 'region_names'";
487-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
493+
const query = SqlParameterQuery.fromSql('mybucket', sql, undefined, {
494+
accept_potentially_dangerous_queries: true
495+
}) as SqlParameterQuery;
488496
expect(query.errors).toEqual([]);
489497
query.id = '1';
490498
expect(query.evaluateParameterRow({ id: 'region1', name: 'colorado' })).toEqual([

packages/sync-rules/test/src/static_parameter_queries.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ describe('static parameter queries', () => {
5656

5757
test('request.parameters()', function () {
5858
const sql = "SELECT request.parameters() ->> 'org_id' as org_id";
59-
const query = SqlParameterQuery.fromSql('mybucket', sql) as StaticSqlParameterQuery;
59+
const query = SqlParameterQuery.fromSql('mybucket', sql, undefined, {
60+
accept_potentially_dangerous_queries: true
61+
}) as StaticSqlParameterQuery;
6062
expect(query.errors).toEqual([]);
6163

6264
expect(query.getStaticBucketIds(normalizeTokenParameters({}, { org_id: 'test' }))).toEqual(['mybucket["test"]']);

packages/sync-rules/test/src/sync_rules.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,40 @@ bucket_definitions:
679679
]);
680680
});
681681

682+
test('dangerous query errors', () => {
683+
const rules = SqlSyncRules.fromYaml(
684+
`
685+
bucket_definitions:
686+
mybucket:
687+
parameters: SELECT request.parameters() ->> 'project_id' as project_id
688+
data: []
689+
`,
690+
{ schema: BASIC_SCHEMA }
691+
);
692+
693+
expect(rules.errors).toMatchObject([
694+
{
695+
message: 'Pontially dangerous query based on unauthenticated client parameters',
696+
type: 'warning'
697+
}
698+
]);
699+
});
700+
701+
test('dangerous query errors - ignored', () => {
702+
const rules = SqlSyncRules.fromYaml(
703+
`
704+
bucket_definitions:
705+
mybucket:
706+
accept_potentially_dangerous_queries: true
707+
parameters: SELECT request.parameters() ->> 'project_id' as project_id
708+
data: []
709+
`,
710+
{ schema: BASIC_SCHEMA }
711+
);
712+
713+
expect(rules.errors).toEqual([]);
714+
});
715+
682716
test('schema generation', () => {
683717
const schema = new StaticSchema([
684718
{

0 commit comments

Comments
 (0)