Skip to content

Conversation

@mistahj67
Copy link
Contributor

@mistahj67 mistahj67 commented Dec 20, 2025

Description

Describe your changes in detail
Added filter support to GET /asset-group-tag/:id/members endpoint for:

  • primary_kind
  • name
  • object_id

Motivation and Context

Resolves BED-6819

Why is this change required? What problem does it solve?

How Has This Been Tested?

Added tests + locally hit the above endpoint with various cases to ensure it was functional

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.

Screenshots (optional):

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

Summary by CodeRabbit

  • New Features

    • Asset group tag members endpoint now supports filtering by name, object_id, and primary_kind with equals, not-equals, and contains/approximate operators for finer query control.
  • Bug Fixes / Validation

    • Request filters are validated server-side; unsupported predicates or non-filterable columns return 400 Bad Request with clear errors.
  • Tests

    • Added coverage for bad predicates/columns and successful name, object_id, and primary_kind filter scenarios.
  • Documentation

    • OpenAPI updated to expose the three new query parameters.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Walkthrough

Adds server-side validation and translation of query-parameter filters (name, object_id, primary_kind) for the AssetGroupTag members endpoint: new AssetGroupMember.ValidFilters(), parsing/validation in GetAssetGroupMembersByTag, graph-DB filter translation, tests, and OpenAPI parameter additions.

Changes

Cohort / File(s) Change Summary
Core implementation
cmd/api/src/api/v2/assetgrouptags.go
Adds AssetGroupMember.ValidFilters(); validates incoming query filters in GetAssetGroupMembersByTag; builds queryFilterMap; translates filters to graph.Criteria via buildAssetGroupMembersByTagGraphDbFilters (ApproximatelyEquals→Contains, NotEquals→Not) and applies them to graph queries; returns 400 for unsupported predicates/columns.
Tests
cmd/api/src/api/v2/assetgrouptags_test.go
Adds test cases to Test_GetAssetGroupMembersByTag for bad predicate, non-filterable column, and successful filtering scenarios for primary_kind, name, and object_id; asserts graph DB pagination/count calls and resulting HTTP responses.
OpenAPI / API docs
packages/go/openapi/doc/openapi.json, packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml
Adds three optional query parameters (primary_kind, name, object_id) using string predicate filter schema to the /api/v2/asset-group-tags/{asset_group_tag_id}/members GET operation.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as "API Handler\nGetAssetGroupMembersByTag"
    participant Validator as "AssetGroupMember.ValidFilters"
    participant Translator as "Filter Translator\nbuildAssetGroupMembersByTagGraphDbFilters"
    participant Graph as "GraphDB"

    Client->>API: GET /asset-group-tags/{id}/members?name~=foo&primary_kind=eq:bar
    API->>Validator: request valid filter map
    Validator-->>API: allowed fields & operators
    API->>API: parse & validate query filters → queryFilterMap
    API->>Translator: translate queryFilterMap → graph.Criteria
    Translator-->>API: graph.Criteria (Contains/Not wraps, combined)
    API->>Graph: Query nodes with graph.Criteria + pagination
    Graph-->>API: nodes + count
    API-->>Client: 200 OK with members
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Possibly related PRs

Suggested labels

api, enhancement, documentation

Suggested reviewers

  • urangel
  • sirisjo
  • irshadaj

Poem

🐰 I hopped through filters, one by one,
Name, ID, and kind beneath the sun,
I checked the rules and built the map,
I nudged the graph — a tidy clap,
Results returned — a joyful run!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding filter support to a specific endpoint with an associated ticket reference.
Description check ✅ Passed The description covers the key sections from the template with implementation details, motivation (Jira ticket), testing approach, and completed checklist items.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-6819

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

@mistahj67 mistahj67 changed the title feat(AGT) add filter support to GET /asset-group-tag/:id/members endpoint BED-6819 feat(AGT): add filter support to GET /asset-group-tag/:id/members endpoint BED-6819 Dec 20, 2025
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)
packages/go/openapi/doc/openapi.json (1)

5514-5546: New member filters look consistent with existing API surface

The added primary_kind, name, and object_id filters correctly mirror the selector-members endpoint and use the standard api.params.predicate.filter.string schema, so the spec matches the new backend filter support.

If you want to tighten the spec further, you could optionally mirror the sort_by enum constraint used on /asset-group-tags/{asset_group_tag_id}/selectors/{asset_group_tag_selector_id}/members so both member-listing endpoints advertise the same explicit sortable columns, but this is non-blocking.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e2e3a8 and 48f3e38.

📒 Files selected for processing (4)
  • cmd/api/src/api/v2/assetgrouptags.go (3 hunks)
  • cmd/api/src/api/v2/assetgrouptags_test.go (1 hunks)
  • packages/go/openapi/doc/openapi.json (1 hunks)
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.
📚 Learning: 2025-07-09T00:36:54.112Z
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.

Applied to files:

  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • packages/go/openapi/doc/openapi.json
  • cmd/api/src/api/v2/assetgrouptags.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/assetgrouptags_test.go
  • cmd/api/src/api/v2/assetgrouptags.go
📚 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/assetgrouptags_test.go
🧬 Code graph analysis (1)
cmd/api/src/api/v2/assetgrouptags_test.go (2)
cmd/api/src/api/constant.go (1)
  • URIPathVariableAssetGroupTagID (56-56)
cmd/api/src/model/filter.go (1)
  • Equals (45-45)
⏰ 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: run-tests
  • GitHub Check: build-ui
  • GitHub Check: run-analysis
🔇 Additional comments (5)
packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml (1)

45-62: LGTM! OpenAPI specification updated correctly.

The three new query parameters (primary_kind, name, object_id) are well-defined with appropriate descriptions and schema references. The parameters are correctly marked as optional and use the predicate filter schema which supports the operators implemented in the backend.

cmd/api/src/api/v2/assetgrouptags_test.go (1)

2340-2420: LGTM! Comprehensive test coverage for the new filters.

The three new test cases effectively validate the filtering functionality:

  • primary_kind filter: Verifies kind-based filtering with equals operator
  • name filter: Tests substring matching with the contains operator (~eq)
  • object_id filter: Tests exclusion with the not-equals operator

Each test correctly constructs the expected graph query filter and verifies the appropriate GraphDB methods are invoked. The tests follow the established pattern and integrate well with the existing test suite.

cmd/api/src/api/v2/assetgrouptags.go (3)

828-834: LGTM! ValidFilters method correctly declares supported filters.

The method appropriately specifies which operators are supported for each filter field:

  • name and object_id support Equals, NotEquals, and ApproximatelyEquals (for substring matching)
  • primary_kind supports only Equals and NotEquals (ApproximatelyEquals doesn't make sense for node kinds)

This aligns well with the OpenAPI specification and the filter building logic.


881-881: LGTM! Filter validation follows best practices.

The query parameter parsing and validation logic correctly:

  1. Parses filters from the request using the standard filter parser
  2. Validates filter names against AssetGroupMember.ValidFilters()
  3. Validates operators against the allowed operators for each field
  4. Returns appropriate 400 errors for invalid filters with clear error messages

The early return pattern ensures invalid requests are rejected before any processing occurs.

Also applies to: 884-914


927-958: LGTM! Graph query filter building is well-implemented.

The filter building logic correctly handles the three filter types:

  1. object_id and name: Maps to graph node properties with appropriate operator handling:

    • ApproximatelyEqualsStringContains (substring match)
    • EqualsEquals (exact match)
  2. primary_kind: Uses query.Kind() which is the correct approach for filtering by graph node type (not a property)

  3. NotEquals: Handled uniformly for all fields by wrapping the predicate in query.Not()

The combination logic is intuitive:

  • Multiple filters for the same field are ORed (e.g., ?name=eq:foo&name=eq:bar → name matches foo OR bar)
  • Filters for different fields are ANDed (e.g., ?name=eq:foo&object_id=eq:123 → name matches foo AND object_id matches 123)

The comment on Line 927 helpfully explains why a bespoke approach is needed for the graph query complexity.

@mistahj67 mistahj67 requested a review from urangel December 22, 2025 17:47
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 (2)
cmd/api/src/api/v2/assetgrouptags.go (2)

927-930: Good integration of the new filter logic.

The graph filter building and integration is correct. The comment could optionally be expanded to clarify that filters within a field group are OR'd, while different fields are AND'd together.


947-983: Verify case sensitivity for approximate matching.

The filter translation logic is well-structured and correctly implements OR within field groups and AND across different fields. The handling of NotEquals by wrapping criteria in query.Not() is correct.

However, ApproximatelyEquals uses query.StringContains (lines 960, 967), which may be case-sensitive. In contrast, SearchAssetGroupTags uses query.CaseInsensitiveStringContains (line 1222) for similar fuzzy matching. Consider verifying whether case-sensitive matching is intentional or if consistency with the search behavior is desired.

Optional: Add defensive default case

While the validation prevents unhandled filter names from reaching the switch statement, adding a default case would make the code more defensive against future modifications:

 		switch queryFilter.Name {
 		case "object_id":
 			ref = query.NodeProperty(common.ObjectID.String())
 			if queryFilter.Operator == model.ApproximatelyEquals {
 				ref = query.StringContains(ref, queryFilter.Value)
 			} else {
 				ref = query.Equals(ref, queryFilter.Value)
 			}
 		case "name":
 			ref = query.NodeProperty(common.Name.String())
 			if queryFilter.Operator == model.ApproximatelyEquals {
 				ref = query.StringContains(ref, queryFilter.Value)
 			} else {
 				ref = query.Equals(ref, queryFilter.Value)
 			}
 		case "primary_kind":
 			ref = query.Kind(query.Node(), graph.StringKind(queryFilter.Value))
+		default:
+			// This should never be reached due to validation, but defend against future changes
+			continue
 		}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48f3e38 and c88ca76.

📒 Files selected for processing (1)
  • cmd/api/src/api/v2/assetgrouptags.go
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.
📚 Learning: 2025-07-09T00:36:54.112Z
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.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/assetgrouptags.go
📚 Learning: 2025-05-27T17:28:48.241Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1503
File: cmd/api/src/services/graphify/tasks.go:68-80
Timestamp: 2025-05-27T17:28:48.241Z
Learning: In Go, avoid using pipelined if/else statements when creating resources that need cleanup (like files, network connections, etc.). The defer semantics work best when resources are created and immediately deferred for cleanup in the same scope. Instead, extract resource management into separate functions where proper defer patterns can be used effectively.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
📚 Learning: 2025-05-27T17:28:46.899Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1503
File: cmd/api/src/services/graphify/tasks.go:114-118
Timestamp: 2025-05-27T17:28:46.899Z
Learning: When processing files in a loop where resources need to be cleaned up, extract the file processing into a separate function that uses `defer` for resource management rather than trying to handle cleanup in the loop body. This ensures proper resource cleanup while maintaining error handling robustness.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.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: run-analysis
  • GitHub Check: run-tests
  • GitHub Check: build-ui
🔇 Additional comments (2)
cmd/api/src/api/v2/assetgrouptags.go (2)

828-834: LGTM! Well-designed filter configuration.

The ValidFilters method correctly defines the supported filters and their operators. The decision to exclude ApproximatelyEquals for primary_kind is appropriate since kind matching should be exact.


884-914: LGTM! Thorough filter validation.

The filter parsing and validation logic is well-structured:

  • Validates filter names against ValidFilters()
  • Ensures operators are supported for each filter
  • Provides clear error messages for invalid filters
  • Fails fast with appropriate 400 responses

Copy link
Contributor

@ALCooper12 ALCooper12 left a comment

Choose a reason for hiding this comment

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

Just needs another commit of just pfc, otherwise LGTM!

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 (2)
cmd/api/src/api/v2/assetgrouptags.go (2)

947-983: Consider adding a default case to the switch statement for robustness.

The switch statement (lines 956-973) handles all currently defined filterable fields from ValidFilters(). However, if someone later adds a new field to ValidFilters() but forgets to update this switch, ref would remain nil and be appended to innerFilters, leading to subtle bugs.

🔎 Proposed defensive default case
 		switch queryFilter.Name {
 		case "object_id":
 			ref = query.NodeProperty(common.ObjectID.String())
 			if queryFilter.Operator == model.ApproximatelyEquals {
 				ref = query.StringContains(ref, queryFilter.Value)
 			} else {
 				ref = query.Equals(ref, queryFilter.Value)
 			}
 		case "name":
 			ref = query.NodeProperty(common.Name.String())
 			if queryFilter.Operator == model.ApproximatelyEquals {
 				ref = query.StringContains(ref, queryFilter.Value)
 			} else {
 				ref = query.Equals(ref, queryFilter.Value)
 			}
 		case "primary_kind":
 			ref = query.Kind(query.Node(), graph.StringKind(queryFilter.Value))
+		default:
+			// This should never happen due to validation, but guard against future changes
+			continue
 		}

Alternatively, add a comment explaining why a default case is not needed:

 		case "primary_kind":
 			ref = query.Kind(query.Node(), graph.StringKind(queryFilter.Value))
+		// No default needed: validation in GetAssetGroupMembersByTag ensures
+		// only fields from ValidFilters() reach this code
 		}

957-970: Optional: Consider reducing duplication between object_id and name cases.

The cases for object_id (lines 957-963) and name (lines 964-970) are identical except for the property name. While this is clear and maintainable as-is, you could optionally extract the common logic if the pattern grows to more fields.

Possible refactoring approach
+		var propertyName string
 		switch queryFilter.Name {
-		case "object_id":
-			ref = query.NodeProperty(common.ObjectID.String())
-			if queryFilter.Operator == model.ApproximatelyEquals {
-				ref = query.StringContains(ref, queryFilter.Value)
-			} else {
-				ref = query.Equals(ref, queryFilter.Value)
-			}
-		case "name":
-			ref = query.NodeProperty(common.Name.String())
+		case "object_id", "name":
+			if queryFilter.Name == "object_id" {
+				propertyName = common.ObjectID.String()
+			} else {
+				propertyName = common.Name.String()
+			}
+			ref = query.NodeProperty(propertyName)
 			if queryFilter.Operator == model.ApproximatelyEquals {
 				ref = query.StringContains(ref, queryFilter.Value)
 			} else {
 				ref = query.Equals(ref, queryFilter.Value)
 			}
 		case "primary_kind":
 			ref = query.Kind(query.Node(), graph.StringKind(queryFilter.Value))
 		}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c88ca76 and b02e75d.

📒 Files selected for processing (4)
  • cmd/api/src/api/v2/assetgrouptags.go
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • packages/go/openapi/doc/openapi.json
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/api/src/api/v2/assetgrouptags_test.go
  • packages/go/openapi/src/paths/asset-isolation.asset-group-tags.id.members.yaml
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.
📚 Learning: 2025-07-09T00:36:54.112Z
Learnt from: mistahj67
Repo: SpecterOps/BloodHound PR: 1648
File: cmd/api/src/api/v2/assetgrouptags.go:763-766
Timestamp: 2025-07-09T00:36:54.112Z
Learning: In cmd/api/src/api/v2/assetgrouptags.go, the SearchAssetGroupTags method intentionally fetches all asset group tags and selectors without database-level filtering because it needs to build a complete `kinds` array from all relevant tags for the graph query filter. This allows members to be searched across all tags of the requested type while still filtering the returned tags/selectors by name match.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
  • packages/go/openapi/doc/openapi.json
📚 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/assetgrouptags.go
📚 Learning: 2025-05-27T17:28:48.241Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1503
File: cmd/api/src/services/graphify/tasks.go:68-80
Timestamp: 2025-05-27T17:28:48.241Z
Learning: In Go, avoid using pipelined if/else statements when creating resources that need cleanup (like files, network connections, etc.). The defer semantics work best when resources are created and immediately deferred for cleanup in the same scope. Instead, extract resource management into separate functions where proper defer patterns can be used effectively.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
📚 Learning: 2025-05-27T17:28:46.899Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1503
File: cmd/api/src/services/graphify/tasks.go:114-118
Timestamp: 2025-05-27T17:28:46.899Z
Learning: When processing files in a loop where resources need to be cleaned up, extract the file processing into a separate function that uses `defer` for resource management rather than trying to handle cleanup in the loop body. This ensures proper resource cleanup while maintaining error handling robustness.

Applied to files:

  • cmd/api/src/api/v2/assetgrouptags.go
🧬 Code graph analysis (1)
cmd/api/src/api/v2/assetgrouptags.go (5)
cmd/api/src/model/filter.go (7)
  • ValidFilters (486-486)
  • FilterOperator (38-38)
  • Equals (45-45)
  • NotEquals (46-46)
  • ApproximatelyEquals (47-47)
  • QueryParameterFilterMap (151-151)
  • NewQueryParameterFilterParser (473-477)
cmd/api/src/api/error.go (4)
  • BuildErrorResponse (134-145)
  • ErrorResponseDetailsBadQueryParameterFilters (43-43)
  • ErrorResponseDetailsColumnNotFilterable (44-44)
  • ErrorResponseDetailsFilterPredicateNotSupported (45-45)
cmd/api/src/api/constant.go (1)
  • URIPathVariableAssetGroupTagID (56-56)
packages/go/graphschema/ad/ad.go (1)
  • DomainSID (155-155)
packages/go/graphschema/azure/azure.go (1)
  • TenantID (107-107)
⏰ 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). (3)
  • GitHub Check: build-ui
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
🔇 Additional comments (4)
packages/go/openapi/doc/openapi.json (1)

5521-5546: New member-filter query params look correct and consistent

primary_kind, name, and object_id are added as optional string predicate filters and match the pattern used on the deprecated /asset-groups/{asset_group_id}/members endpoint and the selector-based members endpoint (same names and api.params.predicate.filter.string schema). JSON structure is valid and the docs (“Filter by …”) are clear.

cmd/api/src/api/v2/assetgrouptags.go (3)

828-834: LGTM! Well-structured filter definition.

The ValidFilters method correctly defines the supported filter fields and their allowed operators. The restriction of primary_kind to exact matching (Equals/NotEquals only) is appropriate since it represents discrete node types.


884-914: LGTM! Filter validation correctly implemented.

The validation loop properly checks that filter fields are valid and operators are supported before building the queryFilterMap. The approach follows the established pattern used in other endpoints (e.g., GetAssetGroupTags). Note that IsStringData is correctly omitted here since these filters translate to graph criteria rather than SQL.


927-930: LGTM! Clean integration of graph filter translation.

The conditional append ensures empty filter results don't affect the query, and the inline comment clearly explains the need for custom translation logic.

Copy link
Contributor

@urangel urangel left a comment

Choose a reason for hiding this comment

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

🚀

@mistahj67 mistahj67 enabled auto-merge (squash) December 22, 2025 22:31
@mistahj67 mistahj67 merged commit 13f419b into main Dec 22, 2025
15 of 17 checks passed
@mistahj67 mistahj67 deleted the BED-6819 branch December 22, 2025 22:34
@github-actions github-actions bot locked and limited conversation to collaborators Dec 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants