Skip to content

Conversation

@slimi-so
Copy link
Contributor

@slimi-so slimi-so commented Jan 11, 2026

Fix: BED-6515

Description

Describe your changes in detail

Motivation and Context

Resolves BED-6515

Why is this change required? What problem does it solve?
To get rid of unexpected Permission Denied! error or "User" and "Power User" roles when they try to Edit/Share their own cypher queries

How Has This Been Tested?

Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc.

Manually on the browser for the users with role "Administrator", "User" and "Power User" and by unit testing the code

Screenshots (optional):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

Summary by CodeRabbit

  • New Features

    • Saved-query sharing: Administrators, Users, and Power Users are now treated as privileged for updating saved-query sharing settings.
  • Tests

    • Test suite refactored to table-driven cases with helpers and expanded coverage across admin, user, and power-user permutations for public, shared, and private query scopes.

✏️ Tip: You can customize this high-level summary in your review settings.

- Fixes Permission Denied! error for User and Power User roles for editing their own cypher queries
- The permission is extended to User and Power User roles from the Administrator role according to permission requirements documentation in https://specterops.atlassian.net/wiki/spaces/~712020bb8a7f8d20094d79bf4a232719fb4cba/pages/1336901652/User+Permissions+Grid
- Adds unit tests for the backend changes

Fix: BED-6515
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 11, 2026

Walkthrough

Broadened privileged-role check in saved query sharing to treat Administrator, User, and Power User as privileged; adjusted control flow accordingly. Tests refactored into a table-driven suite with a new helper and fixtures, expanding coverage across ownership, public/shared scopes, and role permutations. No public signatures changed.

Changes

Cohort / File(s) Summary
Permission logic
cmd/api/src/api/v2/saved_queries_permissions.go
Added local hasPrivilegedRole aggregating Administrator, User, and Power User checks; privileged-path now executes when true, preserving the non-privileged path.
Tests
cmd/api/src/api/v2/saved_queries_permissions_test.go
Converted many subtests to a table-driven suite, added newSavedQueryScope helper, introduced userRoleUser and powerUser fixtures, and expanded coverage across owned/non-owned, public/shared, and role permutations.
Module manifest
go.mod
Minor manifest edits (+7/-1 lines).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

bug, api

Suggested reviewers

  • mistahj67
  • kpowderly
  • urangel

Poem

🐇 I hop through checks with a twitch of my nose,
Admin, User, Power — into one garden they close,
Tests in neat tables, scopes set like a tune,
Ownership balanced beneath a bright moon,
A rabbit applauds this tidy new prose.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix(api): Fixes bug in BED-6515' is vague and non-descriptive, using only the ticket reference without conveying the actual change. Revise the title to clearly describe the main change, such as: 'fix(api): Allow User and Power User roles to edit their own saved queries'
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description includes all required sections from the template with adequate detail about the change, motivation, testing approach, and proper checkbox completion.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
cmd/api/src/api/v2/saved_queries_permissions_test.go (1)

638-648: LGTM! Table-driven test execution is well-structured.

The test loop correctly iterates over all test cases and logs the comment for context.

Minor note: The tc := tc capture (line 639) is no longer necessary in Go 1.22+ due to per-iteration loop variables, but it's harmless and can be cleaned up whenever convenient.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 274a0c1 and 9c98f60.

📒 Files selected for processing (1)
  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts:184-187
Timestamp: 2025-08-27T21:15:32.207Z
Learning: In the BloodHound codebase, syntax fixes for invalid Cypher patterns (like `*..` → `*1..`) may be considered out of scope for specific PRs, even when flagged during review.
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.
Learnt from: benwaples
Repo: SpecterOps/BloodHound PR: 1515
File: packages/javascript/bh-shared-ui/src/views/TierManagement/Save/SelectorForm/SelectorForm.tsx:202-211
Timestamp: 2025-05-30T16:39:53.440Z
Learning: In BloodHound, Cypher injection vulnerabilities are only a concern when `enable_cypher_mutations` is enabled, in which case users already have direct access to execute Cypher via the editor or API endpoints, making string interpolation in queries a non-security issue.
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts:107-109
Timestamp: 2025-08-27T15:14:43.976Z
Learning: In the BloodHound codebase, the saved queries functionality fetches the complete list of queries and performs scope filtering (ALL/OWNED/PUBLIC/SHARED) on the frontend rather than using backend filtering. This approach makes scope-specific cache keys unnecessary since the same comprehensive dataset is always returned from the API.
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-09-08T19:01:53.112Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T19:15:54.854Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/SavedQueryPermissions.tsx:53-54
Timestamp: 2025-08-27T19:15:54.854Z
Learning: In the BloodHound codebase, the team allows the useQueryPermissions hook to run even with undefined queryId because the API is designed to return errors for empty states, and they prefer to handle these error cases naturally rather than preventing the queries from executing.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T15:12:49.129Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts:98-105
Timestamp: 2025-08-27T15:12:49.129Z
Learning: In the BloodHound codebase, for the `useDeleteQueryPermissions` hook in `packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts`, the team prefers to use broad cache invalidation with `queryClient.invalidateQueries(savedQueryKeys.permissions)` rather than targeted invalidation for specific permission entries.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-07-14T22:41:57.442Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1562
File: cmd/api/src/daemons/datapipe/pipeline.go:177-185
Timestamp: 2025-07-14T22:41:57.442Z
Learning: In BloodHound's datapipe pipeline, when checking feature flags before performing actions (like cache reset), the correct pattern is to verify both that the flag retrieval succeeds AND that the flag is enabled. Performing the action when flag retrieval fails is incorrect logic.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-11-05T21:13:08.542Z
Learnt from: ktstrader
Repo: SpecterOps/BloodHound PR: 2020
File: cmd/api/src/queries/graph.go:198-203
Timestamp: 2025-11-05T21:13:08.542Z
Learning: In cmd/api/src/queries/graph.go, when ETAC filtering is enabled in GetAssetGroupComboNode, an empty etacAllowedList (when user.AllEnvironments is false and user.EnvironmentTargetedAccessControl is empty) is intentional and should not be guarded against. The empty list will cause query.In() to filter out all nodes, which is the correct security behavior—users with no environment assignments should see no results.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧬 Code graph analysis (1)
cmd/api/src/api/v2/saved_queries_permissions_test.go (6)
cmd/api/src/database/saved_queries_permissions.go (1)
  • SavedQueryScopeMap (42-42)
cmd/api/src/model/saved_queries_permissions.go (3)
  • SavedQueryScopeOwned (27-27)
  • SavedQueryScopePublic (29-29)
  • SavedQueryScopeShared (28-28)
cmd/api/src/auth/role.go (2)
  • RoleUser (27-27)
  • RolePowerUser (28-28)
cmd/api/src/model/model.go (1)
  • Unique (34-38)
packages/go/schemagen/tsgen/tsgen.go (1)
  • ID (216-220)
cmd/api/src/api/v2/saved_queries_permissions.go (5)
  • SavedQueryPermissionRequest (39-42)
  • ErrForbidden (46-46)
  • ErrInvalidSelfShare (45-45)
  • ErrInvalidPublicShare (47-47)
  • CanUpdateSavedQueriesPermission (50-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
🔇 Additional comments (3)
cmd/api/src/api/v2/saved_queries_permissions_test.go (3)

53-60: LGTM! Clean helper function for building scope maps.

This helper improves test readability by succinctly constructing SavedQueryScopeMap with semantic boolean parameters.


98-124: LGTM! User fixtures properly added for expanded role coverage.

The new userRoleUser and powerUser fixtures correctly use auth.RoleUser and auth.RolePowerUser constants with the AuthManageSelf permission, consistent with the existing fixture patterns.


126-636: Comprehensive table-driven test coverage for permission matrix.

The expanded test cases thoroughly cover:

  • Non-admin owned queries with various scope combinations
  • Admin operating on non-admin owned queries
  • Admin operating on their own queries
  • User role and Power User role scenarios

This provides good coverage for the bug fix that extends edit permissions to "User" and "Power User" roles.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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
Contributor

@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: 1

🤖 Fix all issues with AI agents
In @cmd/api/src/api/v2/saved_queries_permissions_test.go:
- Around line 680-694: Tests allow Public=true with non-empty UserIDs, but the
HTTP handler in saved_queries_permissions.go explicitly rejects that
combination; update CanUpdateSavedQueriesPermission to mirror the handler
validation by returning an error when payload.Public is true and payload.UserIDs
is non-empty (use the same message "Public cannot be true while user_ids is
populated" or equivalent) so permission logic and handler behavior are
consistent; alternatively, remove/adjust the test cases that assert allowing
Public=true with UserIDs if you prefer keeping the current permission function
semantics.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 658f84d and 90edd15.

📒 Files selected for processing (2)
  • cmd/api/src/api/v2/saved_queries_permissions.go
  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts:184-187
Timestamp: 2025-08-27T21:15:32.207Z
Learning: In the BloodHound codebase, syntax fixes for invalid Cypher patterns (like `*..` → `*1..`) may be considered out of scope for specific PRs, even when flagged during review.
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧬 Code graph analysis (2)
cmd/api/src/api/v2/saved_queries_permissions.go (1)
cmd/api/src/auth/role.go (3)
  • RoleAdministrator (29-29)
  • RoleUser (27-27)
  • RolePowerUser (28-28)
cmd/api/src/api/v2/saved_queries_permissions_test.go (4)
cmd/api/src/auth/role.go (2)
  • RoleUser (27-27)
  • RolePowerUser (28-28)
cmd/api/src/api/v2/saved_queries_permissions.go (3)
  • SavedQueryPermissionRequest (39-42)
  • CanUpdateSavedQueriesPermission (50-86)
  • ErrInvalidSelfShare (45-45)
cmd/api/src/database/saved_queries_permissions.go (1)
  • SavedQueryScopeMap (42-42)
cmd/api/src/model/saved_queries_permissions.go (3)
  • SavedQueryScopeOwned (27-27)
  • SavedQueryScopePublic (29-29)
  • SavedQueryScopeShared (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-go-unit-tests
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
🔇 Additional comments (3)
cmd/api/src/api/v2/saved_queries_permissions.go (1)

51-57: LGTM! Permission extension is correctly implemented.

The change successfully extends edit permissions for saved queries from Administrator-only to include User and Power User roles. The boolean aggregation is clear and the comment explains the intent well.

cmd/api/src/api/v2/saved_queries_permissions_test.go (2)

89-115: Test fixtures correctly configured.

The new userRoleUser and powerUser fixtures are properly set up with appropriate role names and permissions, matching the pattern of the existing adminUser fixture.


600-614: Comprehensive test coverage for new role permissions.

These test cases provide thorough coverage of the permission changes, including:

  • Self-share error validation for User and Power User roles
  • Public-to-private query transitions
  • Idempotent operations (setting private queries to private)
  • Converting public+shared queries to private

The tests follow established patterns and effectively validate that User and Power User roles now have the same privileged permissions as Administrators for managing their own saved queries.

Also applies to: 616-630, 632-646, 648-662, 664-678, 696-710, 712-726, 728-742

Comment on lines 680 to 694
t.Run("User-owned, public & shared query shared to other users is allowed", func(t *testing.T) {
payload := v2.SavedQueryPermissionRequest{
UserIDs: []uuid.UUID{nonAdminUser1.ID, nonAdminUser2.ID},
Public: true, // keep it public
}

dbSavedQueryScope := database.SavedQueryScopeMap{
model.SavedQueryScopeOwned: true, // belongs to RoleUser
model.SavedQueryScopePublic: true, // currently public
model.SavedQueryScopeShared: true, // and shared to others
}

err := v2.CanUpdateSavedQueriesPermission(userRoleUser, true, payload, dbSavedQueryScope)
require.Nil(t, err)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check handler validation for Public:true with UserIDs
rg -n "Public cannot be true while user_ids is populated" cmd/api/src/api/v2/saved_queries_permissions.go

Repository: SpecterOps/BloodHound

Length of output: 231


🏁 Script executed:

# Check CanUpdateSavedQueriesPermission function logic
rg -n -B5 -A10 "func CanUpdateSavedQueriesPermission" cmd/api/src/api/v2/saved_queries_permissions.go

Repository: SpecterOps/BloodHound

Length of output: 973


🏁 Script executed:

# Look at the test file to understand the test structure
fd saved_queries_permissions_test.go cmd/api/src/api/v2/

Repository: SpecterOps/BloodHound

Length of output: 117


🏁 Script executed:

# Check lines 680-694 and 744-758 in the test file
sed -n '680,694p; 744,758p' cmd/api/src/api/v2/saved_queries_permissions_test.go

Repository: SpecterOps/BloodHound

Length of output: 1344


Tests verify behavior that the handler explicitly rejects.

The permission function CanUpdateSavedQueriesPermission allows setting Public: true while providing UserIDs, but the handler at line 162 of saved_queries_permissions.go explicitly rejects this combination:

"Public cannot be true while user_ids is populated"

These test cases bypass the handler and verify only the permission function's logic, which permits the scenario. This creates an inconsistency: the handler will reject valid permission function outputs. Additionally, sharing a public query to specific users is logically redundant since public queries are accessible to everyone.

Consider either removing these test cases or adding validation in CanUpdateSavedQueriesPermission to reject Public: true with non-empty UserIDs for consistency with handler validation.

Also applies to: 744-758

🤖 Prompt for AI Agents
In @cmd/api/src/api/v2/saved_queries_permissions_test.go around lines 680 - 694,
Tests allow Public=true with non-empty UserIDs, but the HTTP handler in
saved_queries_permissions.go explicitly rejects that combination; update
CanUpdateSavedQueriesPermission to mirror the handler validation by returning an
error when payload.Public is true and payload.UserIDs is non-empty (use the same
message "Public cannot be true while user_ids is populated" or equivalent) so
permission logic and handler behavior are consistent; alternatively,
remove/adjust the test cases that assert allowing Public=true with UserIDs if
you prefer keeping the current permission function semantics.

…ueriesPermission to a reusable table-drive test suite that loops over various scenarios

- Makes reusable helper function newSavedQueryScope to make custom values and combinations of SavedQueryScopeOwned vs. SavedQueryScopePublic vs. SavedQueryScopeShared
- Makes reusable function runCanUpdateSavedQueriesPermissionTest to loop over json table of various scenarios with custom parameters for:
   - test name
   - test description
   - user role
   - whether user owns the query
   - permission request for userId's to share with
   - permission request for if query will be public
   - previously saved states for whether
      - query was owned by user
      - query was public
      - query was shared to others
Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
cmd/api/src/api/v2/saved_queries_permissions_test.go (1)

536-646: Consider expanding test coverage for User and PowerUser roles.

The User role and PowerUser test cases primarily cover self-share errors and public→private transitions. While these roles share the same privileged code path as Admin, adding a few more cases (e.g., sharing a private query to other users, making a private query public) would provide stronger regression coverage for the bug fix.

This is optional since the core logic is tested via the Admin-owned cases.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90edd15 and fc316b0.

📒 Files selected for processing (1)
  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts:184-187
Timestamp: 2025-08-27T21:15:32.207Z
Learning: In the BloodHound codebase, syntax fixes for invalid Cypher patterns (like `*..` → `*1..`) may be considered out of scope for specific PRs, even when flagged during review.
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-09-08T19:01:53.112Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T19:15:54.854Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/SavedQueryPermissions.tsx:53-54
Timestamp: 2025-08-27T19:15:54.854Z
Learning: In the BloodHound codebase, the team allows the useQueryPermissions hook to run even with undefined queryId because the API is designed to return errors for empty states, and they prefer to handle these error cases naturally rather than preventing the queries from executing.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T15:12:49.129Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts:98-105
Timestamp: 2025-08-27T15:12:49.129Z
Learning: In the BloodHound codebase, for the `useDeleteQueryPermissions` hook in `packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts`, the team prefers to use broad cache invalidation with `queryClient.invalidateQueries(savedQueryKeys.permissions)` rather than targeted invalidation for specific permission entries.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-07-14T22:41:57.442Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1562
File: cmd/api/src/daemons/datapipe/pipeline.go:177-185
Timestamp: 2025-07-14T22:41:57.442Z
Learning: In BloodHound's datapipe pipeline, when checking feature flags before performing actions (like cache reset), the correct pattern is to verify both that the flag retrieval succeeds AND that the flag is enabled. Performing the action when flag retrieval fails is incorrect logic.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-11-05T21:13:08.542Z
Learnt from: ktstrader
Repo: SpecterOps/BloodHound PR: 2020
File: cmd/api/src/queries/graph.go:198-203
Timestamp: 2025-11-05T21:13:08.542Z
Learning: In cmd/api/src/queries/graph.go, when ETAC filtering is enabled in GetAssetGroupComboNode, an empty etacAllowedList (when user.AllEnvironments is false and user.EnvironmentTargetedAccessControl is empty) is intentional and should not be guarded against. The empty list will cause query.In() to filter out all nodes, which is the correct security behavior—users with no environment assignments should see no results.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧬 Code graph analysis (1)
cmd/api/src/api/v2/saved_queries_permissions_test.go (4)
cmd/api/src/database/saved_queries_permissions.go (1)
  • SavedQueryScopeMap (42-42)
cmd/api/src/model/saved_queries_permissions.go (3)
  • SavedQueryScopeOwned (27-27)
  • SavedQueryScopePublic (29-29)
  • SavedQueryScopeShared (28-28)
cmd/api/src/api/v2/saved_queries_permissions.go (5)
  • SavedQueryPermissionRequest (39-42)
  • CanUpdateSavedQueriesPermission (50-86)
  • ErrForbidden (46-46)
  • ErrInvalidSelfShare (45-45)
  • ErrInvalidPublicShare (47-47)
cmd/api/src/auth/role.go (2)
  • RoleUser (27-27)
  • RolePowerUser (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
🔇 Additional comments (4)
cmd/api/src/api/v2/saved_queries_permissions_test.go (4)

53-60: LGTM!

Clean helper that reduces boilerplate for constructing scope maps in tests.


62-83: LGTM!

Well-structured test helper with proper t.Helper() usage and clear parameter organization.


121-147: LGTM!

New user fixtures for RoleUser and RolePowerUser are consistent with existing patterns and correctly support testing the extended privilege model.


649-660: LGTM!

The test execution loop correctly iterates over all cases and invokes the helper with appropriate arguments.

…_CanUpdateSavedQueriesPermission to get rid of extra unnecessary wrapper runCanUpdateSavedQueriesPermissionTest
…anUpdateSavedQueriesPermission in saved_queries_permissions_test.go for the case, non admin Non-privileged user cannot make someone else's public query private
Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@cmd/api/src/api/v2/saved_queries_permissions_test.go`:
- Around line 268-279: The test suite contains a duplicate test case named
"Non-admin owned, public query set to public error" (same payload, user
nonAdminUser1, scope from newSavedQueryScope(true, true, false), and expectedErr
v2.ErrForbidden); remove the duplicate entry or replace it with a distinct
scenario (e.g., change the payload, ownership flag, user, or expectedErr) so
each test case in saved_queries_permissions_test.go is unique; locate the
duplicate by searching for the test name and the v2.SavedQueryPermissionRequest
payload and delete or modify the redundant block.
♻️ Duplicate comments (1)
cmd/api/src/api/v2/saved_queries_permissions_test.go (1)

574-585: Test verifies behavior that the handler explicitly rejects.

This test case passes Public: true with a non-empty UserIDs list, but the inline comment acknowledges that the "HTTP handler will reject this combination." Testing permission logic for an input that can never reach this code path provides limited value and may give false confidence about test coverage.

🧹 Nitpick comments (1)
cmd/api/src/api/v2/saved_queries_permissions_test.go (1)

650-660: LGTM!

The test loop correctly captures the range variable and iterates through all test cases. Since these subtests are self-contained with no shared mocks or state, you could optionally add t.Parallel() after the t.Run line to speed up test execution. Based on learnings, this pattern is acceptable for self-contained subtests.

♻️ Optional: Add subtest parallelization
 	for _, tc := range tests {
 		tc := tc // capture range variable
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			if tc.comment != "" {
 				t.Log(tc.comment)
 			}
 
 			err := v2.CanUpdateSavedQueriesPermission(tc.user, tc.savedQueryBelongsToUser, tc.payload, tc.scope)
 			require.Equal(t, tc.expectedErr, err)
 		})
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06edbc8 and 274a0c1.

📒 Files selected for processing (1)
  • cmd/api/src/api/v2/saved_queries_permissions_test.go
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/commonSearchesAGT.ts:184-187
Timestamp: 2025-08-27T21:15:32.207Z
Learning: In the BloodHound codebase, syntax fixes for invalid Cypher patterns (like `*..` → `*1..`) may be considered out of scope for specific PRs, even when flagged during review.
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-09-08T19:01:53.112Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx:108-148
Timestamp: 2025-09-08T19:01:53.112Z
Learning: In BloodHound's CypherSearch component (packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/CypherSearch.tsx), the sharing state reset for sharedIds and isPublic after the two-step permissions update is handled elsewhere in the codebase, so additional state reset callbacks in the updateQueryPermissions function are not needed.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T19:15:54.854Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/SavedQueries/SavedQueryPermissions.tsx:53-54
Timestamp: 2025-08-27T19:15:54.854Z
Learning: In the BloodHound codebase, the team allows the useQueryPermissions hook to run even with undefined queryId because the API is designed to return errors for empty states, and they prefer to handle these error cases naturally rather than preventing the queries from executing.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-08-27T15:12:49.129Z
Learnt from: jvacca-specterops
Repo: SpecterOps/BloodHound PR: 1823
File: packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts:98-105
Timestamp: 2025-08-27T15:12:49.129Z
Learning: In the BloodHound codebase, for the `useDeleteQueryPermissions` hook in `packages/javascript/bh-shared-ui/src/hooks/useSavedQueries.ts`, the team prefers to use broad cache invalidation with `queryClient.invalidateQueries(savedQueryKeys.permissions)` rather than targeted invalidation for specific permission entries.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-07-14T22:41:57.442Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1562
File: cmd/api/src/daemons/datapipe/pipeline.go:177-185
Timestamp: 2025-07-14T22:41:57.442Z
Learning: In BloodHound's datapipe pipeline, when checking feature flags before performing actions (like cache reset), the correct pattern is to verify both that the flag retrieval succeeds AND that the flag is enabled. Performing the action when flag retrieval fails is incorrect logic.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
📚 Learning: 2025-11-05T21:13:08.542Z
Learnt from: ktstrader
Repo: SpecterOps/BloodHound PR: 2020
File: cmd/api/src/queries/graph.go:198-203
Timestamp: 2025-11-05T21:13:08.542Z
Learning: In cmd/api/src/queries/graph.go, when ETAC filtering is enabled in GetAssetGroupComboNode, an empty etacAllowedList (when user.AllEnvironments is false and user.EnvironmentTargetedAccessControl is empty) is intentional and should not be guarded against. The empty list will cause query.In() to filter out all nodes, which is the correct security behavior—users with no environment assignments should see no results.

Applied to files:

  • cmd/api/src/api/v2/saved_queries_permissions_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
🔇 Additional comments (2)
cmd/api/src/api/v2/saved_queries_permissions_test.go (2)

53-60: LGTM!

Clean helper function that improves test readability by succinctly constructing scope maps.


98-124: LGTM!

Test fixtures for userRoleUser and powerUser are properly constructed with appropriate roles and permissions to exercise the expanded privilege checks.

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