From d4df5b879b19b38e9301d927125bf77e621265cd Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 15:25:31 +0200 Subject: [PATCH] Reimplement dangerous request parameter check with visitor --- .changeset/chilled-lamps-hammer.md | 5 ++ packages/sync-rules/src/SqlParameterQuery.ts | 26 ++++---- .../sync-rules/src/StaticSqlParameterQuery.ts | 22 +++---- .../TableValuedFunctionSqlParameterQuery.ts | 36 +++++++---- packages/sync-rules/src/request_functions.ts | 13 +++- packages/sync-rules/src/sql_filters.ts | 47 +++++--------- packages/sync-rules/src/sql_support.ts | 30 ++++----- packages/sync-rules/src/types.ts | 47 +++++++++----- packages/sync-rules/src/validators.ts | 62 +++++++++++++++++++ 9 files changed, 186 insertions(+), 102 deletions(-) create mode 100644 .changeset/chilled-lamps-hammer.md create mode 100644 packages/sync-rules/src/validators.ts diff --git a/.changeset/chilled-lamps-hammer.md b/.changeset/chilled-lamps-hammer.md new file mode 100644 index 000000000..bb01313a4 --- /dev/null +++ b/.changeset/chilled-lamps-hammer.md @@ -0,0 +1,5 @@ +--- +'@powersync/service-sync-rules': patch +--- + +Remove `usesAuthenticatedRequestParameters` and `usesUnauthenticatedRequestParameters` in favor of `DetectRequestParameters` computing them on-demand. diff --git a/packages/sync-rules/src/SqlParameterQuery.ts b/packages/sync-rules/src/SqlParameterQuery.ts index cfc465969..76a83a5f0 100644 --- a/packages/sync-rules/src/SqlParameterQuery.ts +++ b/packages/sync-rules/src/SqlParameterQuery.ts @@ -30,6 +30,7 @@ import { SqliteRow } from './types.js'; import { filterJsonRow, getBucketId, isJsonValue, isSelectStatement, normalizeParameterValue } from './utils.js'; +import { DetectRequestParameters } from './validators.js'; export interface SqlParameterQueryOptions { sourceTable: TablePattern; @@ -496,30 +497,29 @@ export class SqlParameterQuery { get hasAuthenticatedBucketParameters(): boolean { // select request.user_id() as user_id where ... - const authenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesAuthenticatedRequestParameters - ) != null; - return authenticatedExtractor; + const visitor = new DetectRequestParameters(); + visitor.acceptAll(Object.values(this.parameterExtractors)); + + return visitor.usesAuthenticatedRequestParameters; } get hasAuthenticatedMatchClause(): boolean { // select ... where user_id = request.user_id() - const authenticatedInputParameter = this.filter.usesAuthenticatedRequestParameters; - return authenticatedInputParameter; + const visitor = new DetectRequestParameters(); + visitor.accept(this.filter); + return visitor.usesAuthenticatedRequestParameters; } get usesUnauthenticatedRequestParameters(): boolean { + const visitor = new DetectRequestParameters(); + // select ... where request.parameters() ->> 'include_comments' - const unauthenticatedInputParameter = this.filter.usesUnauthenticatedRequestParameters; + visitor.accept(this.filter); // select request.parameters() ->> 'project_id' - const unauthenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesUnauthenticatedRequestParameters - ) != null; + visitor.acceptAll(Object.values(this.parameterExtractors)); - return unauthenticatedInputParameter || unauthenticatedExtractor; + return visitor.usesUnauthenticatedRequestParameters; } /** diff --git a/packages/sync-rules/src/StaticSqlParameterQuery.ts b/packages/sync-rules/src/StaticSqlParameterQuery.ts index 35ea8c5b5..9a5d05f66 100644 --- a/packages/sync-rules/src/StaticSqlParameterQuery.ts +++ b/packages/sync-rules/src/StaticSqlParameterQuery.ts @@ -11,6 +11,7 @@ import { SqliteJsonValue } from './types.js'; import { getBucketId, isJsonValue } from './utils.js'; +import { DetectRequestParameters } from './validators.js'; export interface StaticSqlParameterQueryOptions { sql: string; @@ -194,26 +195,21 @@ export class StaticSqlParameterQuery { // select where request.jwt() ->> 'role' == 'authorized' // we do not count this as a sufficient check // const authenticatedFilter = this.filter.usesAuthenticatedRequestParameters; + const visitor = new DetectRequestParameters(); + visitor.acceptAll(Object.values(this.parameterExtractors)); - // select request.user_id() as user_id - const authenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesAuthenticatedRequestParameters - ) != null; - return authenticatedExtractor; + return visitor.usesAuthenticatedRequestParameters; } get usesUnauthenticatedRequestParameters(): boolean { + const visitor = new DetectRequestParameters(); + // select where request.parameters() ->> 'include_comments' - const unauthenticatedFilter = this.filter?.usesUnauthenticatedRequestParameters; + visitor.accept(this.filter); // select request.parameters() ->> 'project_id' - const unauthenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesUnauthenticatedRequestParameters - ) != null; - - return unauthenticatedFilter || unauthenticatedExtractor; + visitor.acceptAll(Object.values(this.parameterExtractors)); + return visitor.usesUnauthenticatedRequestParameters; } get usesDangerousRequestParameters() { diff --git a/packages/sync-rules/src/TableValuedFunctionSqlParameterQuery.ts b/packages/sync-rules/src/TableValuedFunctionSqlParameterQuery.ts index b4b4430f6..8b8da15bf 100644 --- a/packages/sync-rules/src/TableValuedFunctionSqlParameterQuery.ts +++ b/packages/sync-rules/src/TableValuedFunctionSqlParameterQuery.ts @@ -14,6 +14,7 @@ import { } from './types.js'; import { getBucketId, isJsonValue } from './utils.js'; import { BucketDescription, BucketPriority, DEFAULT_BUCKET_PRIORITY } from './BucketDescription.js'; +import { DetectRequestParameters } from './validators.js'; export interface TableValuedFunctionSqlParameterQueryOptions { sql: string; @@ -260,37 +261,46 @@ export class TableValuedFunctionSqlParameterQuery { }; } + private visitParameterExtractorsAndCallClause(): DetectRequestParameters { + const visitor = new DetectRequestParameters(); + + // e.g. select request.user_id() as user_id + visitor.acceptAll(Object.values(this.parameterExtractors)); + + // e.g. select value from json_each(request.jwt() ->> 'project_ids') + visitor.accept(this.callClause); + + return visitor; + } + get hasAuthenticatedBucketParameters(): boolean { // select where request.jwt() ->> 'role' == 'authorized' // we do not count this as a sufficient check // const authenticatedFilter = this.filter.usesAuthenticatedRequestParameters; + const visitor = new DetectRequestParameters(); // select request.user_id() as user_id - const authenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesAuthenticatedRequestParameters - ) != null; + visitor.acceptAll(Object.values(this.parameterExtractors)); // select value from json_each(request.jwt() ->> 'project_ids') - const authenticatedArgument = this.callClause?.usesAuthenticatedRequestParameters ?? false; + visitor.accept(this.callClause); - return authenticatedExtractor || authenticatedArgument; + return visitor.usesAuthenticatedRequestParameters; } get usesUnauthenticatedRequestParameters(): boolean { + const visitor = new DetectRequestParameters(); + // select where request.parameters() ->> 'include_comments' - const unauthenticatedFilter = this.filter?.usesUnauthenticatedRequestParameters; + visitor.accept(this.filter); // select request.parameters() ->> 'project_id' - const unauthenticatedExtractor = - Object.values(this.parameterExtractors).find( - (clause) => isParameterValueClause(clause) && clause.usesUnauthenticatedRequestParameters - ) != null; + visitor.acceptAll(Object.values(this.parameterExtractors)); // select value from json_each(request.parameters() ->> 'project_ids') - const unauthenticatedArgument = this.callClause?.usesUnauthenticatedRequestParameters ?? false; + visitor.accept(this.callClause); - return unauthenticatedFilter || unauthenticatedExtractor || unauthenticatedArgument; + return visitor.usesUnauthenticatedRequestParameters; } get usesDangerousRequestParameters() { diff --git a/packages/sync-rules/src/request_functions.ts b/packages/sync-rules/src/request_functions.ts index c77ff9a67..637a44d5e 100644 --- a/packages/sync-rules/src/request_functions.ts +++ b/packages/sync-rules/src/request_functions.ts @@ -1,7 +1,7 @@ import { ExpressionType } from './ExpressionType.js'; import { CompatibilityContext, CompatibilityEdition, CompatibilityOption } from './compatibility.js'; import { generateSqlFunctions } from './sql_functions.js'; -import { ParameterValueSet, SqliteValue } from './types.js'; +import { CompiledClause, ParameterValueClause, ParameterValueSet, SqliteValue } from './types.js'; export interface SqlParameterFunction { readonly debugName: string; @@ -131,3 +131,14 @@ const REQUEST_FUNCTIONS_NAMED = { }; export const REQUEST_FUNCTIONS: Record = REQUEST_FUNCTIONS_NAMED; + +/** + * A {@link ParameterValueClause} derived from a call to a {@link SqlParameterFunction}. + */ +export interface RequestFunctionCall extends ParameterValueClause { + function: SqlParameterFunction; +} + +export function isRequestFunctionCall(clause: CompiledClause): clause is RequestFunctionCall { + return (clause as RequestFunctionCall).function != null; +} diff --git a/packages/sync-rules/src/sql_filters.ts b/packages/sync-rules/src/sql_filters.ts index dde2a73ea..2bcb92be1 100644 --- a/packages/sync-rules/src/sql_filters.ts +++ b/packages/sync-rules/src/sql_filters.ts @@ -4,7 +4,7 @@ import { nil } from 'pgsql-ast-parser/src/utils.js'; import { BucketPriority, isValidPriority } from './BucketDescription.js'; import { ExpressionType } from './ExpressionType.js'; import { SqlRuleError } from './errors.js'; -import { REQUEST_FUNCTIONS, SqlParameterFunction } from './request_functions.js'; +import { REQUEST_FUNCTIONS, RequestFunctionCall, SqlParameterFunction } from './request_functions.js'; import { BASIC_OPERATORS, OPERATOR_IN, @@ -35,6 +35,7 @@ import { ClauseError, CompiledClause, InputParameter, + LegacyParameterFromTableClause, ParameterMatchClause, ParameterValueClause, QueryParameters, @@ -419,14 +420,14 @@ export class SqlTools { const parameterArguments = compiledArguments as ParameterValueClause[]; return { + function: impl, key: `${schema}.${fn}(${parameterArguments.map((p) => p.key).join(',')})`, lookupParameterValue(parameters) { const evaluatedArgs = parameterArguments.map((p) => p.lookupParameterValue(parameters)); return impl.call(parameters, ...evaluatedArgs); }, - usesAuthenticatedRequestParameters: impl.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: impl.usesUnauthenticatedRequestParameters - } satisfies ParameterValueClause; + visitChildren: (v) => parameterArguments.forEach(v) + } satisfies RequestFunctionCall; } } @@ -494,8 +495,7 @@ export class SqlTools { return { [inputParam.key]: value }; }); }, - usesAuthenticatedRequestParameters: leftFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: leftFilter.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(leftFilter) } satisfies ParameterMatchClause; } else if ( this.supportsExpandingParameters && @@ -530,8 +530,7 @@ export class SqlTools { } return [{ [inputParam.key]: value }]; }, - usesAuthenticatedRequestParameters: rightFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: rightFilter.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(rightFilter) } satisfies ParameterMatchClause; } else { // Not supported, return the error previously computed @@ -579,8 +578,7 @@ export class SqlTools { return { [inputParam.key]: value }; }); }, - usesAuthenticatedRequestParameters: leftFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: leftFilter.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(leftFilter) } satisfies ParameterMatchClause; } else if ( this.supportsExpandingParameters && @@ -622,8 +620,7 @@ export class SqlTools { return { [inputParam.key]: value }; }); }, - usesAuthenticatedRequestParameters: rightFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: rightFilter.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(rightFilter) } satisfies ParameterMatchClause; } else { // Not supported, return the error previously computed @@ -652,8 +649,7 @@ export class SqlTools { return [{ [inputParam.key]: value }]; }, - usesAuthenticatedRequestParameters: otherFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: otherFilter.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(otherFilter) } satisfies ParameterMatchClause; } @@ -776,17 +772,16 @@ export class SqlTools { } } - private getParameterRefClause(expr: ExprRef): ParameterValueClause { + private getParameterRefClause(expr: ExprRef): LegacyParameterFromTableClause { const table = AvailableTable.search(expr.table?.name ?? this.defaultTable!, this.parameterTables)!.nameInSchema; const column = expr.name; return { + table, key: `${table}.${column}`, lookupParameterValue: (parameters) => { return parameters.lookup(table, column); - }, - usesAuthenticatedRequestParameters: table == 'token_parameters', - usesUnauthenticatedRequestParameters: table == 'user_parameters' - } satisfies ParameterValueClause; + } + } satisfies LegacyParameterFromTableClause; } refHasSchema(ref: ExprRef) { @@ -861,12 +856,7 @@ export class SqlTools { } else if (argsType == 'param') { const argStrings = argClauses.map((e) => (e as ParameterValueClause).key); const name = `${fnImpl.debugName}(${argStrings.join(',')})`; - const usesAuthenticatedRequestParameters = - argClauses.find((clause) => isParameterValueClause(clause) && clause.usesAuthenticatedRequestParameters) != - null; - const usesUnauthenticatedRequestParameters = - argClauses.find((clause) => isParameterValueClause(clause) && clause.usesUnauthenticatedRequestParameters) != - null; + return { key: name, lookupParameterValue: (parameters) => { @@ -881,8 +871,7 @@ export class SqlTools { }); return fnImpl.call(...args); }, - usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters + visitChildren: (v) => argClauses.forEach(v) } satisfies ParameterValueClause; } else { throw new Error('unreachable condition'); @@ -983,9 +972,7 @@ function staticValueClause(value: SqliteValue): StaticValueClause { key: JSONBig.stringify(value), lookupParameterValue(_parameters) { return value; - }, - usesAuthenticatedRequestParameters: false, - usesUnauthenticatedRequestParameters: false + } }; } diff --git a/packages/sync-rules/src/sql_support.ts b/packages/sync-rules/src/sql_support.ts index 01e638d89..3ffb28b18 100644 --- a/packages/sync-rules/src/sql_support.ts +++ b/packages/sync-rules/src/sql_support.ts @@ -97,8 +97,7 @@ export function composeParameterValues e[1].usesAuthenticatedRequestParameters), - usesUnauthenticatedRequestParameters: entries.some((e) => e[1].usesUnauthenticatedRequestParameters), + visitChildren: (visitor) => entries.forEach((e) => visitor(e[1])), key: `${options.key}${entries.map((e) => e[1].key).join(',')}`, lookupParameterValue: function (parameters: ParameterValueSet): SqliteValue { const evaluated = Object.fromEntries( @@ -195,10 +194,10 @@ export function andFilters(a: CompiledClause, b: CompiledClause): CompiledClause } return results; }, - usesAuthenticatedRequestParameters: - aFilter.usesAuthenticatedRequestParameters || bFilter.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: - aFilter.usesUnauthenticatedRequestParameters || bFilter.usesUnauthenticatedRequestParameters + visitChildren: (visitor) => { + visitor(aFilter); + visitor(bFilter); + } } satisfies ParameterMatchClause; } @@ -262,11 +261,11 @@ export function orParameterSetClauses(a: ParameterMatchClause, b: ParameterMatch let results: FilterParameters[] = [...aResult, ...bResult]; return results; }, - // Pessimistic check - usesAuthenticatedRequestParameters: a.usesAuthenticatedRequestParameters && b.usesAuthenticatedRequestParameters, - // Optimistic check - usesUnauthenticatedRequestParameters: - a.usesUnauthenticatedRequestParameters || b.usesUnauthenticatedRequestParameters + specialType: 'or', + visitChildren: (v) => { + v(a); + v(b); + } } satisfies ParameterMatchClause; } @@ -287,8 +286,7 @@ export function toBooleanParameterSetClause(clause: CompiledClause): ParameterMa const value = sqliteBool(clause.evaluate(tables)); return value ? MATCH_CONST_TRUE : MATCH_CONST_FALSE; }, - usesAuthenticatedRequestParameters: false, - usesUnauthenticatedRequestParameters: false + visitChildren: (v) => v(clause) } satisfies ParameterMatchClause; } else if (isClauseError(clause)) { return { @@ -298,8 +296,7 @@ export function toBooleanParameterSetClause(clause: CompiledClause): ParameterMa filterRow(tables: QueryParameters): TrueIfParametersMatch { throw new Error('invalid clause'); }, - usesAuthenticatedRequestParameters: false, - usesUnauthenticatedRequestParameters: false + visitChildren: (v) => v(clause) } satisfies ParameterMatchClause; } else { // Equivalent to `bucket.param = true` @@ -324,8 +321,7 @@ export function toBooleanParameterSetClause(clause: CompiledClause): ParameterMa filterRow(tables: QueryParameters): TrueIfParametersMatch { return [{ [key]: SQLITE_TRUE }]; }, - usesAuthenticatedRequestParameters: clause.usesAuthenticatedRequestParameters, - usesUnauthenticatedRequestParameters: clause.usesUnauthenticatedRequestParameters + visitChildren: (v) => v(clause) } satisfies ParameterMatchClause; } } diff --git a/packages/sync-rules/src/types.ts b/packages/sync-rules/src/types.ts index 842f726f6..8a607428c 100644 --- a/packages/sync-rules/src/types.ts +++ b/packages/sync-rules/src/types.ts @@ -8,6 +8,7 @@ import { BucketPriority } from './BucketDescription.js'; import { ParameterLookup } from './BucketParameterQuerier.js'; import { CustomSqliteValue } from './types/custom_sqlite_value.js'; import { CompatibilityContext } from './compatibility.js'; +import { RequestFunctionCall } from './request_functions.js'; export interface SyncRules { evaluateRow(options: EvaluateRowOptions): EvaluationResult[]; @@ -316,6 +317,13 @@ export interface TableRow { record: R; } +export interface BaseClause { + /** + * If this clause is composed from semantic child-clauses, calls the given callback function for each child. + */ + visitChildren?: (visitor: (clause: CompiledClause) => void) => void; +} + /** * This is a clause that matches row and parameter values for equality. * @@ -324,9 +332,11 @@ export interface TableRow { * * For a given a row, this produces a set of parameters that would make the clause evaluate to true. */ -export interface ParameterMatchClause { +export interface ParameterMatchClause extends BaseClause { error: boolean; + specialType?: 'or'; + /** * The parameter fields that are used for this filter, for example: * * ['bucket.region_id'] for a data query @@ -358,22 +368,12 @@ export interface ParameterMatchClause { * @return The filter parameters */ filterRow(tables: QueryParameters): TrueIfParametersMatch; - - /** request.user_id(), request.jwt(), token_parameters.* */ - usesAuthenticatedRequestParameters: boolean; - /** request.parameters(), user_parameters.* */ - usesUnauthenticatedRequestParameters: boolean; } /** * This is a clause that operates on request or bucket parameters. */ -export interface ParameterValueClause { - /** request.user_id(), request.jwt(), token_parameters.* */ - usesAuthenticatedRequestParameters: boolean; - /** request.parameters(), user_parameters.* */ - usesUnauthenticatedRequestParameters: boolean; - +export interface ParameterValueClause extends BaseClause { /** * An unique key for the clause. * @@ -390,6 +390,17 @@ export interface ParameterValueClause { lookupParameterValue(parameters: ParameterValueSet): SqliteValue; } +/** + * A {@link ParameterValueClause} created by selecting from the legacy `token_parameters` or `user_parameters` tables. + */ +export interface LegacyParameterFromTableClause extends ParameterValueClause { + table: string; +} + +export function isLegacyParameterFromTableClause(clause: CompiledClause): clause is LegacyParameterFromTableClause { + return (clause as LegacyParameterFromTableClause).table != null; +} + export interface QuerySchema { /** * @param table The unaliased table, as it appears in the source schema. @@ -409,7 +420,7 @@ export interface QuerySchema { * For parameter queries, that is the parameter table being queried. * For data queries, that is the data table being queried. */ -export interface RowValueClause { +export interface RowValueClause extends BaseClause { evaluate(tables: QueryParameters): SqliteValue; getColumnDefinition(schema: QuerySchema): ColumnDefinition | undefined; } @@ -423,11 +434,17 @@ export interface StaticValueClause extends RowValueClause, ParameterValueClause readonly value: SqliteValue; } -export interface ClauseError { +export interface ClauseError extends BaseClause { error: true; } -export type CompiledClause = RowValueClause | ParameterMatchClause | ParameterValueClause | ClauseError; +export type CompiledClause = + | RowValueClause + | ParameterMatchClause + | ParameterValueClause + | RequestFunctionCall // extends ParameterValueClause + | LegacyParameterFromTableClause // extends ParameterValueClause + | ClauseError; /** * true if any of the filter parameter sets match diff --git a/packages/sync-rules/src/validators.ts b/packages/sync-rules/src/validators.ts new file mode 100644 index 000000000..b28b5f999 --- /dev/null +++ b/packages/sync-rules/src/validators.ts @@ -0,0 +1,62 @@ +import { isRequestFunctionCall } from './request_functions.js'; +import { isParameterMatchClause } from './sql_support.js'; +import { CompiledClause, isLegacyParameterFromTableClause, ParameterMatchClause } from './types.js'; + +/** + * Detects the use of request parameters in a compiled clause. + */ +export class DetectRequestParameters { + /** request.user_id(), request.jwt(), token_parameters.* */ + usesAuthenticatedRequestParameters: boolean = false; + /** request.parameters(), user_parameters.* */ + usesUnauthenticatedRequestParameters: boolean = false; + + accept(clause?: CompiledClause) { + if (clause == null) { + return null; + } + + if (isRequestFunctionCall(clause)) { + const f = clause.function; + + this.usesAuthenticatedRequestParameters ||= f.usesAuthenticatedRequestParameters; + this.usesUnauthenticatedRequestParameters ||= f.usesUnauthenticatedRequestParameters; + } else if (isLegacyParameterFromTableClause(clause)) { + const table = clause.table; + if (table == 'token_parameters') { + this.usesAuthenticatedRequestParameters = true; + } else if (table == 'user_parameters') { + this.usesUnauthenticatedRequestParameters = true; + } + } else if (isParameterMatchClause(clause) && clause.specialType == 'or') { + // We only treat this clause as using authenticated request parameters if all subclauses use authenticated request + // parameters. + const leftVisitor = new DetectRequestParameters(); + const rightVisitor = new DetectRequestParameters(); + + let isFirstChild = true; + clause.visitChildren!((v) => { + if (isFirstChild) { + leftVisitor.accept(v); + } else { + rightVisitor.accept(v); + } + }); + this.usesAuthenticatedRequestParameters = + leftVisitor.usesAuthenticatedRequestParameters && rightVisitor.usesAuthenticatedRequestParameters; + + // For unauthenticated parameters, it's enough if either side reads unauthenticated data. + this.usesUnauthenticatedRequestParameters ||= leftVisitor.usesUnauthenticatedRequestParameters; + this.usesUnauthenticatedRequestParameters ||= rightVisitor.usesUnauthenticatedRequestParameters; + return; + } + + clause.visitChildren?.((c) => this.accept(c)); + } + + acceptAll(clauses: Iterable) { + for (const clause of clauses) { + this.accept(clause); + } + } +}