Skip to content

feat: implement table read permission checks for saved queries and add utility functions for query table resolution#1804

Merged
Artuomka merged 1 commit into
mainfrom
backend_dashboard_permissions
May 27, 2026
Merged

feat: implement table read permission checks for saved queries and add utility functions for query table resolution#1804
Artuomka merged 1 commit into
mainfrom
backend_dashboard_permissions

Conversation

@Artuomka
Copy link
Copy Markdown
Collaborator

@Artuomka Artuomka commented May 27, 2026

Summary by CodeRabbit

  • New Features

    • Enforced table-level read permissions when executing saved queries and running ad-hoc queries; users lacking access to referenced tables receive permission-denied responses.
  • Tests

    • Added end-to-end test coverage for table-level authorization enforcement across various SQL patterns and edge cases.
    • Updated existing test assertions for output format accuracy.

Review Change Stack

…d utility functions for query table resolution
Copilot AI review requested due to automatic review settings May 27, 2026 11:32
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

This PR adds table-level Cedar authorization enforcement for saved query execution and ad-hoc query testing. It introduces utilities to extract table names from SQL queries, enforces read permissions per table, integrates authorization into two use cases, and validates behavior with comprehensive E2E tests.

Changes

Table-level Authorization for Queries

Layer / File(s) Summary
SQL query table extraction
backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts
collectQueryTables parses SQL via node-sql-parser to extract referenced table names for a given connection type, returning either resolved table names or indeterminate with a reason (non-SQL type, parse failure, no concrete tables).
Query table extraction tests
backend/test/ava-tests/non-saas-tests/non-saas-collect-query-tables.test.ts
Unit tests validate collectQueryTables across SQL patterns (SELECT/JOIN/subquery/UNION/CTE/schema qualifiers) and error cases (non-SQL connections, unparseable queries, indeterminate queries).
Table-level authorization assertion
backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts, backend/src/exceptions/text/messages.ts
assertUserCanReadQueryTables enforces Cedar read permissions on extracted tables, falling back to all-table checks when resolution is indeterminate, and throws ForbiddenException with table-specific messages on denial. New error message for denied access.
Saved query execution authorization
backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts
ExecuteSavedDbQueryUseCase injects CedarAuthorizationService and enforces table-level read permissions via assertUserCanReadQueryTables before executing saved queries.
Ad-hoc query testing authorization
backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts
TestDbQueryUseCase injects CedarAuthorizationService and enforces table-level read permissions via assertUserCanReadQueryTables before executing test queries; DAO creation moved earlier to support authorization checks.
End-to-end authorization tests
backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts
Comprehensive NestJS E2E tests validate Cedar authorization for saved queries and ad-hoc testing, covering grant/deny scenarios per table, indeterminate query fallback behavior, and interactions with Cedar policies at connection/table/panel levels.
Test assertion update
backend/test/ava-tests/connection-diagram-preview.test.ts
Minor fix to existing test assertion to expect literal [NEW] marker (with escaped brackets) in Mermaid column labels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • lyubov-voloshko
  • gugu

Poem

🐰 A rabbit hops through Cedar's trees,
Where tables now require permissions, please!
With parsers parsing, checks enforcing tight,
Saved queries dance in authorization's light.
Each table checked, each user validated true—
A feature built with tests and logic through! ✨

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: implementing table-level read permission checks for saved queries and adding utility functions for query table resolution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed PR implements table-level Cedar authorization checks with secure fallback validation and comprehensive testing, with no security regressions to existing checks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend_dashboard_permissions

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Artuomka Artuomka enabled auto-merge May 27, 2026 11:33
@coderabbitai coderabbitai Bot requested review from gugu and lyubov-voloshko May 27, 2026 11:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds table-level read permission enforcement for executing ad-hoc queries and saved queries by resolving referenced tables from SQL (when possible) and falling back to an “all tables must be readable” check when table resolution is indeterminate.

Changes:

  • Introduces utilities to (a) resolve referenced tables from a SQL query and (b) enforce table:read across those tables (with an all-tables fallback).
  • Enforces table read checks in saved-query execution and /query/test use cases via Cedar authorization.
  • Adds AVA coverage for query-table resolution and SaaS e2e permission scenarios; updates a Mermaid diagram preview regex expectation.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts New SaaS e2e tests validating table read enforcement for /query/test and saved-query execution.
backend/test/ava-tests/non-saas-tests/non-saas-collect-query-tables.test.ts Unit tests for SQL table collection behavior (including indeterminate cases).
backend/test/ava-tests/connection-diagram-preview.test.ts Adjusts regex expectation for [NEW] marker formatting in Mermaid output.
backend/src/exceptions/text/messages.ts Adds a dedicated “no read permission for table” message used by the new guard logic.
backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts New utility to parse SQL and extract referenced tables (or return indeterminate with reason).
backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts New utility enforcing table:read across resolved tables, with an all-tables fallback.
backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts Applies table read enforcement before executing ad-hoc test queries.
backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts Applies table read enforcement before executing saved queries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +36
if (NON_SQL_CONNECTION_TYPES.has(connectionType)) {
return { kind: 'indeterminate', reason: `non-SQL connection type "${connectionType}"` };
}

const dialect = connectionTypeToParserDialect(connectionType);
tablesToCheck = collected.tables;
} else {
slackPostMessage(
`Saved-query permission check could not resolve referenced tables for connection ${connectionId} ` +
Comment on lines +43 to +47
for (const tableName of tablesToCheck) {
const allowed = await validateTableRead(tableName);
if (!allowed) {
throw new ForbiddenException(Messages.NO_READ_PERMISSION_FOR_TABLE(tableName));
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts (1)

71-88: 💤 Low value

Optional: Consider consolidating parsing to avoid double work.

The query is parsed twice: once via tableList() (line 42) and again via astify() (line 73). You could extract both table references and CTE names from a single AST traversal. However, the current approach is clearer and the performance cost is negligible for authorization checks that run once per query execution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts`
around lines 71 - 88, The code parses the SQL twice (tableList() and
collectCteNames() calling parser.astify()); to fix, consolidate parsing by
parsing once and reusing the AST: modify collectCteNames or tableList to accept
an AST (e.g., change collectCteNames signature to accept ast: any or add a new
collectTablesAndCtes(parser: Parser, query: string, dialect: string) that calls
parser.astify() once and traverses the AST to extract both table references and
CTE names, updating callers to use the new API (reference functions
collectCteNames, tableList and parser.astify) so you avoid duplicate
parser.astify() calls.
backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts (2)

43-48: ⚖️ Poor tradeoff

Sequential permission checks may cause latency for queries touching many tables.

Each table is validated sequentially via await validateTableRead(tableName). For queries referencing many tables (e.g., large JOINs or the all-tables fallback), this could be slow. Consider parallelizing with Promise.all if the authorization service supports concurrent requests.

♻️ Optional parallel validation
-	for (const tableName of tablesToCheck) {
-		const allowed = await validateTableRead(tableName);
-		if (!allowed) {
-			throw new ForbiddenException(Messages.NO_READ_PERMISSION_FOR_TABLE(tableName));
-		}
-	}
+	const results = await Promise.all(
+		tablesToCheck.map(async (tableName) => ({
+			tableName,
+			allowed: await validateTableRead(tableName),
+		})),
+	);
+	const denied = results.find((r) => !r.allowed);
+	if (denied) {
+		throw new ForbiddenException(Messages.NO_READ_PERMISSION_FOR_TABLE(denied.tableName));
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts`
around lines 43 - 48, The loop that calls validateTableRead sequentially should
be converted to run validations in parallel: map tablesToCheck to an array of
Promises by calling validateTableRead(tableName) for each table, await
Promise.all on that array, then inspect results and if any validation returned
falsy throw the existing ForbiddenException using
Messages.NO_READ_PERMISSION_FOR_TABLE with the corresponding tableName; this
keeps the same error behavior but avoids sequential awaits and reduces latency
for large tablesToCheck collections.

36-39: 💤 Low value

Consider sanitizing or truncating the query in Slack messages.

The full query text is included in the Slack message. If queries may contain sensitive data (e.g., literals with PII), this could leak sensitive information to Slack. Consider truncating or omitting the query body, or redacting potential literals.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts`
around lines 36 - 39, The Slack alert currently includes the full query text via
the variable query in the slackPostMessage call (alongside connectionId and
collected.reason), which may leak sensitive literals; update the code to
sanitize or truncate the query before sending — e.g., implement and call a
helper like sanitizeQuery(query) or truncateQuery(query, maxLen) that
strips/replaces literal values and/or limits length, then pass the sanitized
value to slackPostMessage instead of the raw query; ensure the helper is used
wherever the raw query was previously logged.
backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts (2)

45-53: 💤 Low value

Error logging in test.after swallows the error silently.

If cleanup fails, the error is logged but the test suite still appears to pass. Consider re-throwing or using t.fail() if cleanup is critical, or at minimum ensure this doesn't mask issues in CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts`
around lines 45 - 53, The cleanup catch block in the test.after hook silences
failures (in the block that calls Cacher.clearAllCache(), deletes
process.env.CEDAR_AUTHORIZATION_ENABLED, and awaits app.close()), so change the
handler to surface failures instead of swallowing them: after logging the error,
re-throw it (or remove the try/catch so the thrown error fails the suite);
ensure the test.after for saved-query-table-permissions-e2e.test.ts does not
swallow exceptions from Cacher.clearAllCache or app.close so CI sees the
failure.

19-21: 💤 Low value

Minor: _testUtils is declared but unused.

The _testUtils variable is retrieved from the module but never used in the tests. If not needed, consider removing it to reduce noise.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts`
around lines 19 - 21, The _testUtils local is declared but never used; remove
the unused declaration and any code that retrieves or assigns TestUtils from the
testing module (the variable named _testUtils) to eliminate dead code—update the
setup/teardown in the file where _testUtils is created (keep INestApplication
app and currentTest intact) so no references to _testUtils remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts`:
- Around line 43-48: The loop that calls validateTableRead sequentially should
be converted to run validations in parallel: map tablesToCheck to an array of
Promises by calling validateTableRead(tableName) for each table, await
Promise.all on that array, then inspect results and if any validation returned
falsy throw the existing ForbiddenException using
Messages.NO_READ_PERMISSION_FOR_TABLE with the corresponding tableName; this
keeps the same error behavior but avoids sequential awaits and reduces latency
for large tablesToCheck collections.
- Around line 36-39: The Slack alert currently includes the full query text via
the variable query in the slackPostMessage call (alongside connectionId and
collected.reason), which may leak sensitive literals; update the code to
sanitize or truncate the query before sending — e.g., implement and call a
helper like sanitizeQuery(query) or truncateQuery(query, maxLen) that
strips/replaces literal values and/or limits length, then pass the sanitized
value to slackPostMessage instead of the raw query; ensure the helper is used
wherever the raw query was previously logged.

In
`@backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts`:
- Around line 71-88: The code parses the SQL twice (tableList() and
collectCteNames() calling parser.astify()); to fix, consolidate parsing by
parsing once and reusing the AST: modify collectCteNames or tableList to accept
an AST (e.g., change collectCteNames signature to accept ast: any or add a new
collectTablesAndCtes(parser: Parser, query: string, dialect: string) that calls
parser.astify() once and traverses the AST to extract both table references and
CTE names, updating callers to use the new API (reference functions
collectCteNames, tableList and parser.astify) so you avoid duplicate
parser.astify() calls.

In
`@backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts`:
- Around line 45-53: The cleanup catch block in the test.after hook silences
failures (in the block that calls Cacher.clearAllCache(), deletes
process.env.CEDAR_AUTHORIZATION_ENABLED, and awaits app.close()), so change the
handler to surface failures instead of swallowing them: after logging the error,
re-throw it (or remove the try/catch so the thrown error fails the suite);
ensure the test.after for saved-query-table-permissions-e2e.test.ts does not
swallow exceptions from Cacher.clearAllCache or app.close so CI sees the
failure.
- Around line 19-21: The _testUtils local is declared but never used; remove the
unused declaration and any code that retrieves or assigns TestUtils from the
testing module (the variable named _testUtils) to eliminate dead code—update the
setup/teardown in the file where _testUtils is created (keep INestApplication
app and currentTest intact) so no references to _testUtils remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c709ef81-4823-4ed1-9d11-9a855556e80c

📥 Commits

Reviewing files that changed from the base of the PR and between 8c01dfc and bb24e5a.

📒 Files selected for processing (8)
  • backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts
  • backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts
  • backend/src/entities/visualizations/panel/utils/assert-query-tables-readable.util.ts
  • backend/src/entities/visualizations/panel/utils/collect-query-tables.util.ts
  • backend/src/exceptions/text/messages.ts
  • backend/test/ava-tests/connection-diagram-preview.test.ts
  • backend/test/ava-tests/non-saas-tests/non-saas-collect-query-tables.test.ts
  • backend/test/ava-tests/saas-tests/saas-saved-query-table-permissions-e2e.test.ts

@Artuomka Artuomka merged commit 4d3e905 into main May 27, 2026
19 checks passed
@Artuomka Artuomka deleted the backend_dashboard_permissions branch May 27, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants