Skip to content

Add panel permissions and guards, update authorization logic, and enhance tests#1713

Merged
Artuomka merged 1 commit intomainfrom
backend_dashboards_permissions
Apr 9, 2026
Merged

Add panel permissions and guards, update authorization logic, and enhance tests#1713
Artuomka merged 1 commit intomainfrom
backend_dashboards_permissions

Conversation

@Artuomka
Copy link
Copy Markdown
Collaborator

@Artuomka Artuomka commented Apr 9, 2026

Summary by CodeRabbit

  • New Features

    • Added panel-level permissions with granular access control (read, create, edit, delete) enabling fine-grained authorization for individual panels
    • Implemented user-scoped panel filtering to display only panels a user has permission to access
    • Enhanced authorization validation to support panel-specific permission checks
  • Security

    • Strengthened access control mechanisms with panel-level authorization gates on read and edit operations

…ance tests

- Introduced new panel-related actions in CedarAction and CedarResourceType enums.
- Updated CedarValidationRequest to include panelId for validation.
- Enhanced CedarAuthorizationService to handle panel permissions.
- Modified buildCedarEntities to include panel entities.
- Updated policy generation and parsing to support panel permissions.
- Created PanelEditGuard and PanelReadGuard for panel access control.
- Updated panel controller to use new guards for CRUD operations.
- Enhanced FindAllPanels use case to check permissions before returning results.
- Added comprehensive end-to-end tests for panel permissions, ensuring correct access control.
Copilot AI review requested due to automatic review settings April 9, 2026 12:57
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Adds Cedar-based authorization support for panel (saved query) resources by introducing panel-specific actions, guards, and authorization validation throughout the backend authorization flow, enabling granular permission control at the panel level alongside existing connection and dashboard scoping.

Changes

Cohort / File(s) Summary
Cedar Schema & Type Definitions
backend/src/entities/cedar-authorization/cedar-action-map.ts, backend/src/entities/cedar-authorization/cedar-schema.ts, backend/src/entities/permission/permission.interface.ts
Added new Panel entity type, four panel action variants (panel:read, panel:create, panel:edit, panel:delete), and corresponding permission data structures (IPanelAccessLevel, IPanelPermissionData) to support panel-level authorization.
Cedar Authorization Services
backend/src/entities/cedar-authorization/cedar-authorization.service.ts, backend/src/entities/cedar-authorization/cedar-entity-builder.ts, backend/src/entities/cedar-authorization/cedar-policy-generator.ts, backend/src/entities/cedar-authorization/cedar-policy-parser.ts
Extended authorization service to handle panel-scoped validation, updated entity builder to include panel entities in Cedar entities, added panel policy generation and parsing logic following the existing dashboard pattern, and modified signatures to thread panelId through the authorization stack.
Authorization Guards
backend/src/guards/panel-read.guard.ts, backend/src/guards/panel-edit.guard.ts
Implemented two new NestJS guards for protecting panel endpoints: PanelReadGuard validates panel read access with fallback to connection membership, while PanelEditGuard determines action type (PanelCreate, PanelEdit, PanelDelete) based on HTTP method and validates accordingly.
Controller & Use Case Updates
backend/src/entities/visualizations/panel/panel.controller.ts, backend/src/entities/visualizations/panel/use-cases/find-all-panels.use.case.ts, backend/src/entities/visualizations/panel/data-structures/find-all-panels.ds.ts
Replaced generic guards with panel-specific guards, added userId parameter threading to support authorization checks, and updated find-all-panels use case to filter results based per-panel Cedar authorization validation.
Widget Controller Updates
backend/src/entities/visualizations/panel-position/panel-position.controller.ts
Replaced ConnectionEditGuard with DashboardEditGuard for widget CRUD endpoints while preserving ConnectionEditGuard for AI-related endpoints.
E2E Tests
backend/test/ava-tests/saas-tests/saas-dashboard-permissions-e2e.test.ts
Added comprehensive test coverage for panel permission authorization scenarios including listing, retrieval, creation, and permission denial cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • lyubov-voloshko

Poem

🐰 Panels now guard themselves with care,
Cedar actions floating through the air!
Read, create, edit, delete so fine,
Authorization checks in perfect line.
Per-panel permissions bloom anew,
Access granted only to the few! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Security Check ⚠️ Warning Pull request contains critical security vulnerability: new sentinel in Cedar policy grants panel:read when ANY user has panel read permission, combined with default to new on missing panelId, creating authorization bypass. Remove panel:read from new sentinel (only panel:create should apply), add panelId format validation in guards, implement panelId presence validation, ensure consistent @UserId decorator usage, and add defensive checks for missing panelId.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: adding panel permissions, new authorization guards, updating authorization logic, and enhancing tests with panel-related E2E tests.

✏️ 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_dashboards_permissions

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.

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

This PR extends the Cedar-based authorization model to cover Panels (Saved Queries), wiring new panel-specific guards into the panel endpoints and updating policy/schema tooling so panel permissions can be generated, parsed, and enforced. It also tightens dashboard widget authorization by using dashboard-level guards and adds E2E coverage for the new panel permission behaviors.

Changes:

  • Add Cedar schema/actions/entities support for panel:* permissions and extend policy generator/parser accordingly.
  • Introduce PanelReadGuard/PanelEditGuard and apply them to saved-query endpoints; filter panel listings by per-panel permissions.
  • Expand SaaS E2E tests to verify list/read/create behaviors under panel permission policies.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
backend/test/ava-tests/saas-tests/saas-dashboard-permissions-e2e.test.ts Adds E2E coverage for panel (saved query) permissions (list/read/create cases).
backend/src/guards/panel-read.guard.ts New guard enforcing panel:read for single-panel reads (and connection membership for list).
backend/src/guards/panel-edit.guard.ts New guard enforcing panel:create/panel:edit/panel:delete based on HTTP method and presence of panel id.
backend/src/entities/visualizations/panel/use-cases/find-all-panels.use.case.ts Filters returned panels by Cedar panel:read authorization.
backend/src/entities/visualizations/panel/panel.controller.ts Switches saved-query endpoints to panel-specific guards and passes userId into list use case.
backend/src/entities/visualizations/panel/data-structures/find-all-panels.ds.ts Adds userId to support per-user authorization filtering in list use case.
backend/src/entities/visualizations/panel-position/panel-position.controller.ts Uses DashboardEditGuard for widget create/update/delete to enforce dashboard-level permissions.
backend/src/entities/permission/permission.interface.ts Adds panel permissions shape to the classical permissions model.
backend/src/entities/cedar-authorization/cedar-schema.ts Extends Cedar schema with Panel entity type and panel:* actions.
backend/src/entities/cedar-authorization/cedar-policy-parser.ts Parses panel:* permits into classical panels permission data.
backend/src/entities/cedar-authorization/cedar-policy-generator.ts Generates Cedar permits for panel:* permissions, including __new__ sentinel behavior.
backend/src/entities/cedar-authorization/cedar-entity-builder.ts Adds Cedar entity construction for Panel resources.
backend/src/entities/cedar-authorization/cedar-authorization.service.ts Adds panel-aware validation routing and foreign-connection reference checks for panel resources.
backend/src/entities/cedar-authorization/cedar-action-map.ts Adds Panel* actions/resource type and validation request support for panelId.

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

Comment on lines +40 to +44
const accessChecks = await Promise.all(
foundQueries.map((query) =>
this.cedarAuthService.validate({
userId,
action: CedarAction.PanelRead,
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Promise.all(foundQueries.map(... cedarAuthService.validate ...)) triggers one full Cedar validation per panel. Each validate() call hits the DB (user suspension + group lookup) and evaluates policies, so listing panels becomes O(N) DB work and can also fan out many concurrent queries at once. Consider adding a CedarAuthorizationService helper that loads user groups/policies once per request and evaluates multiple panelIds using the same precomputed data (or at least limit concurrency), then filter based on those results.

Copilot uses AI. Check for mistakes.
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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
backend/src/entities/cedar-authorization/cedar-policy-parser.ts (1)

286-330: Prefer const arrow helpers for the new panel parsing utilities.

These helpers were introduced as function declarations, while the repo rule for new TS/JS helpers prefers arrow functions. As per coding guidelines, "Prefer arrow functions over function declarations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/entities/cedar-authorization/cedar-policy-parser.ts` around lines
286 - 330, Replace the three function declarations with const arrow helpers:
convert function extractPanelId(resourceId: string | null, connectionId:
string): string | null { ... } into const extractPanelId = (resourceId: string |
null, connectionId: string): string | null => { ... }, convert
getOrCreatePanelEntry(map: Map<string, IPanelPermissionData>, panelId: string):
IPanelPermissionData { ... } into const getOrCreatePanelEntry = (map:
Map<string, IPanelPermissionData>, panelId: string): IPanelPermissionData => {
... }, and convert applyPanelAction(entry: IPanelPermissionData, action:
string): void { ... } into const applyPanelAction = (entry:
IPanelPermissionData, action: string): void => { ... }; keep all parameter and
return types, body logic, and existing Map/get/set behavior unchanged and ensure
you use const for each helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/src/entities/cedar-authorization/cedar-policy-generator.ts`:
- Around line 117-121: Remove the code that emits a permit for
RocketAdmin::Action::"panel:read" against the __new__ sentinel (newPanelRef); do
not grant panel:read for `RocketAdmin::Panel::"${connectionId}/__new__"`.
Instead, if you need to allow operations on the sentinel, emit only a permit for
RocketAdmin::Action::"panel:create" against newPanelRef (or rely on the existing
create-permission variable/logic), and keep panel:read permits scoped only to
concrete panel IDs (leave the hasPanelReadPermission handling for real panel
resources untouched).

In
`@backend/src/entities/visualizations/panel/use-cases/find-all-panels.use.case.ts`:
- Around line 40-51: The current code calls this.cedarAuthService.validate for
each item in foundQueries causing N DB/policy loads; instead, perform a single
per-request authorization step and then filter locally: either add/use a batch
authorization method on cedarAuthService (e.g.,
validateBatch/authorizePanelsForUser) that accepts userId, connectionId, action
= CedarAction.PanelRead and an array of panelIds and returns a Set/Map of
allowed panelIds, or create a single request auth context (e.g.,
cedarAuthService.createAuthContext(userId, connectionId)) that preloads
assertUserNotSuspended, user groups, and policies and exposes an
isAuthorized(panelId) check; then replace the Promise.all loop over foundQueries
with one batch call and filter foundQueries by the returned allowed IDs before
mapping to buildFoundPanelDto(query).

In `@backend/test/ava-tests/saas-tests/saas-dashboard-permissions-e2e.test.ts`:
- Around line 284-287: The test descriptions use the shared variable currentTest
which is still set to "Dashboard permissions", so update the currentTest
assignment to a panel-specific label (e.g., "Panel permissions" or "Panel (Saved
Query) permissions") before the PANEL (SAVED QUERY) PERMISSIONS block so
test.serial `${currentTest} should allow listing panels...` reflects the correct
context; locate and change the currentTest variable used by the panel tests (the
currentTest identifier referenced in the PANEL (SAVED QUERY) PERMISSIONS section
and surrounding tests) to the new string.

---

Nitpick comments:
In `@backend/src/entities/cedar-authorization/cedar-policy-parser.ts`:
- Around line 286-330: Replace the three function declarations with const arrow
helpers: convert function extractPanelId(resourceId: string | null,
connectionId: string): string | null { ... } into const extractPanelId =
(resourceId: string | null, connectionId: string): string | null => { ... },
convert getOrCreatePanelEntry(map: Map<string, IPanelPermissionData>, panelId:
string): IPanelPermissionData { ... } into const getOrCreatePanelEntry = (map:
Map<string, IPanelPermissionData>, panelId: string): IPanelPermissionData => {
... }, and convert applyPanelAction(entry: IPanelPermissionData, action:
string): void { ... } into const applyPanelAction = (entry:
IPanelPermissionData, action: string): void => { ... }; keep all parameter and
return types, body logic, and existing Map/get/set behavior unchanged and ensure
you use const for each helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b88ee545-6679-4d31-be5e-c1c483b8f377

📥 Commits

Reviewing files that changed from the base of the PR and between bc7e6cf and f887989.

📒 Files selected for processing (14)
  • backend/src/entities/cedar-authorization/cedar-action-map.ts
  • backend/src/entities/cedar-authorization/cedar-authorization.service.ts
  • backend/src/entities/cedar-authorization/cedar-entity-builder.ts
  • backend/src/entities/cedar-authorization/cedar-policy-generator.ts
  • backend/src/entities/cedar-authorization/cedar-policy-parser.ts
  • backend/src/entities/cedar-authorization/cedar-schema.ts
  • backend/src/entities/permission/permission.interface.ts
  • backend/src/entities/visualizations/panel-position/panel-position.controller.ts
  • backend/src/entities/visualizations/panel/data-structures/find-all-panels.ds.ts
  • backend/src/entities/visualizations/panel/panel.controller.ts
  • backend/src/entities/visualizations/panel/use-cases/find-all-panels.use.case.ts
  • backend/src/guards/panel-edit.guard.ts
  • backend/src/guards/panel-read.guard.ts
  • backend/test/ava-tests/saas-tests/saas-dashboard-permissions-e2e.test.ts

Comment on lines +117 to +121
const newPanelRef = `RocketAdmin::Panel::"${connectionId}/__new__"`;
if (hasPanelReadPermission) {
policies.push(
`permit(\n principal,\n action == RocketAdmin::Action::"panel:read",\n resource == ${newPanelRef}\n);`,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not grant panel:read on the __new__ sentinel.

cedar-authorization.service.ts falls back to __new__ when a panel read is evaluated without a panelId. Emitting a read permit for that sentinel means a missing panelId can succeed against __new__ instead of the real panel. __new__ only needs panel:create; keep panel:read scoped to concrete panel IDs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/entities/cedar-authorization/cedar-policy-generator.ts` around
lines 117 - 121, Remove the code that emits a permit for
RocketAdmin::Action::"panel:read" against the __new__ sentinel (newPanelRef); do
not grant panel:read for `RocketAdmin::Panel::"${connectionId}/__new__"`.
Instead, if you need to allow operations on the sentinel, emit only a permit for
RocketAdmin::Action::"panel:create" against newPanelRef (or rely on the existing
create-permission variable/logic), and keep panel:read permits scoped only to
concrete panel IDs (leave the hasPanelReadPermission handling for real panel
resources untouched).

Comment on lines +40 to +51
const accessChecks = await Promise.all(
foundQueries.map((query) =>
this.cedarAuthService.validate({
userId,
action: CedarAction.PanelRead,
connectionId,
panelId: query.id,
}),
),
);

return foundQueries.filter((_, index) => accessChecks[index]).map((query) => buildFoundPanelDto(query));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid per-panel validate() calls in the list path.

This loop turns one listing request into N authorization evaluations, and validate() is doing DB work (assertUserNotSuspended, findAllUserGroupsInConnection, policy loading) on each iteration. With a few hundred panels this endpoint will degrade sharply. Please batch this or reuse a single per-request authorization context before filtering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/entities/visualizations/panel/use-cases/find-all-panels.use.case.ts`
around lines 40 - 51, The current code calls this.cedarAuthService.validate for
each item in foundQueries causing N DB/policy loads; instead, perform a single
per-request authorization step and then filter locally: either add/use a batch
authorization method on cedarAuthService (e.g.,
validateBatch/authorizePanelsForUser) that accepts userId, connectionId, action
= CedarAction.PanelRead and an array of panelIds and returns a Set/Map of
allowed panelIds, or create a single request auth context (e.g.,
cedarAuthService.createAuthContext(userId, connectionId)) that preloads
assertUserNotSuspended, user groups, and policies and exposes an
isAuthorized(panelId) check; then replace the Promise.all loop over foundQueries
with one batch call and filter foundQueries by the returned allowed IDs before
mapping to buildFoundPanelDto(query).

Comment on lines +284 to +287
//****************************** PANEL (SAVED QUERY) PERMISSIONS ******************************

test.serial(
`${currentTest} should allow listing panels when user has read access to a specific panel via cedar policy`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update currentTest variable for panel tests.

The currentTest variable is still set to 'Dashboard permissions' from line 60, causing all panel test descriptions to incorrectly display as "Dashboard permissions should allow listing panels..." instead of the expected panel-specific context.

🐛 Proposed fix
 //****************************** PANEL (SAVED QUERY) PERMISSIONS ******************************

+currentTest = 'Panel permissions';
+
 test.serial(
 	`${currentTest} should allow listing panels when user has read access to a specific panel via cedar policy`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//****************************** PANEL (SAVED QUERY) PERMISSIONS ******************************
test.serial(
`${currentTest} should allow listing panels when user has read access to a specific panel via cedar policy`,
//****************************** PANEL (SAVED QUERY) PERMISSIONS ******************************
currentTest = 'Panel permissions';
test.serial(
`${currentTest} should allow listing panels when user has read access to a specific panel via cedar policy`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/saas-dashboard-permissions-e2e.test.ts`
around lines 284 - 287, The test descriptions use the shared variable
currentTest which is still set to "Dashboard permissions", so update the
currentTest assignment to a panel-specific label (e.g., "Panel permissions" or
"Panel (Saved Query) permissions") before the PANEL (SAVED QUERY) PERMISSIONS
block so test.serial `${currentTest} should allow listing panels...` reflects
the correct context; locate and change the currentTest variable used by the
panel tests (the currentTest identifier referenced in the PANEL (SAVED QUERY)
PERMISSIONS section and surrounding tests) to the new string.

@Artuomka Artuomka merged commit 528b32d into main Apr 9, 2026
22 of 23 checks passed
@Artuomka Artuomka deleted the backend_dashboards_permissions branch April 9, 2026 13:11
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