Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

OSAC-58: filter unpublished catalog items from public API - #595

Merged
openshift-merge-bot[bot] merged 9 commits into
osac-project:mainfrom
tzvatot:OSAC-58/filter-unpublished-catalog-items
Jun 1, 2026
Merged

openshift-merge-bot[bot] merged 9 commits into
osac-project:mainfrom
tzvatot:OSAC-58/filter-unpublished-catalog-items

Conversation

@tzvatot

@tzvatot tzvatot commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Public List endpoints for cluster and compute instance catalog items now inject a this.published == true CEL filter, excluding unpublished items from results
  • Public Get endpoints return NotFound for unpublished catalog items
  • Private API endpoints are unaffected - admins can still see all items

Changes

  • catalog_item_validation.go: add addPublishedFilter helper that appends the published CEL clause to user-provided filters
  • cluster_catalog_items_server.go: apply filter in List, add published check in Get
  • compute_instance_catalog_items_server.go: same changes
  • New catalog_item_validation_test.go: table-driven tests for addPublishedFilter
  • Updated existing tests to set Published: true explicitly
  • Added tests: list excludes unpublished, list with user filter excludes unpublished, get returns not found for unpublished

Test plan

  • All 800 server unit tests pass (60 catalog-related)
  • addPublishedFilter table-driven tests cover empty, simple, and compound filter inputs
  • Unpublished items excluded from List (with and without user-provided filter)
  • Unpublished items return NotFound from Get
  • Published items unaffected in List, Get, Update, Delete
  • E2E: test_unpublished_catalog_item_not_visible_in_public_api (blocked on deployment)

Summary by CodeRabbit

  • New Features

    • Public List/Get now enforce published-only visibility; non-published items are hidden from other users.
    • Incoming filter expressions are syntax-validated; malformed filters are rejected with an error.
  • Behavior Changes

    • Retrieving an unpublished item owned by another user returns "not found"; creators can still access their unpublished items.
  • Tests

    • Added and updated tests covering publication visibility, filter validation, and NotFound behavior.

Review Change Stack

@openshift-ci-robot

openshift-ci-robot commented May 27, 2026 •

Copy link
Copy Markdown

@tzvatot: This pull request references OSAC-58 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the epic to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Public List endpoints for cluster and compute instance catalog items now inject a this.published == true CEL filter, excluding unpublished items from results
  • Public Get endpoints return NotFound for unpublished catalog items
  • Private API endpoints are unaffected - admins can still see all items

Changes

  • catalog_item_validation.go: add addPublishedFilter helper that appends the published CEL clause to user-provided filters
  • cluster_catalog_items_server.go: apply filter in List, add published check in Get
  • compute_instance_catalog_items_server.go: same changes
  • New catalog_item_validation_test.go: table-driven tests for addPublishedFilter
  • Updated existing tests to set Published: true explicitly
  • Added tests: list excludes unpublished, list with user filter excludes unpublished, get returns not found for unpublished

Test plan

  • All 800 server unit tests pass (60 catalog-related)
  • addPublishedFilter table-driven tests cover empty, simple, and compound filter inputs
  • Unpublished items excluded from List (with and without user-provided filter)
  • Unpublished items return NotFound from Get
  • Published items unaffected in List, Get, Update, Delete
  • E2E: test_unpublished_catalog_item_not_visible_in_public_api (blocked on deployment)

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested review from adriengentil and eranco74 May 27, 2026 13:37
@coderabbitai

coderabbitai Bot commented May 27, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds CEL syntax validation and an addPublishedFilter helper that composes user-provided CEL filters with this.published; List handlers use the composed filter and Get handlers return gRPC NotFound for unpublished items. Tests updated/added across cluster and compute servers to assert these behaviors.

Changes

Catalog Item Published Visibility

Layer / File(s) Summary
Published filter helper and unit tests
internal/servers/catalog_item_validation.go, internal/servers/catalog_item_validation_test.go
Adds validateCELSyntax (package-level CEL env) and addPublishedFilter() to compose/validate user CEL filters with this.published; tests assert exact composition and reject malformed filters.
ClusterCatalogItemsServer published enforcement and tests
internal/servers/cluster_catalog_items_server.go, internal/servers/cluster_catalog_items_server_test.go
List() now composes the private filter via addPublishedFilter() and returns InvalidArgument on syntax errors; Get() returns gRPC NotFound for unpublished private objects. Tests updated to create published fixtures and add list/get visibility cases.
ComputeInstanceCatalogItemsServer published enforcement and tests
internal/servers/compute_instance_catalog_items_server.go, internal/servers/compute_instance_catalog_items_server_test.go
Mirrors cluster changes: List() composes filters with addPublishedFilter() and validates CEL syntax; Get() rejects unpublished objects with gRPC NotFound. Tests updated/expanded similarly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • osac-project/fulfillment-service#536: Introduces the ClusterCatalogItemsServer and ComputeInstanceCatalogItemsServer implementations that this PR modifies to enforce published-only visibility.

Suggested labels

lgtm

Suggested reviewers

  • adriengentil
  • jhernand

Poem

A guard on lists and gets does stand,
Published only for public land.
CEL checks whisper, filters bind,
Hidden drafts remain confined. ✨

Security Considerations

Risk Severity: Low | Impact: Access Control Hardening

  • The change hardens access control by hiding unpublished catalog items from public List and Get endpoints; Get returns gRPC NotFound for unpublished items and List excludes unpublished items.
  • User-supplied CEL filters are validated before composition to prevent malformed-filter bypass or syntax injection attempts; composed filter is parenthesized and combined with this.published.
🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Ai-Attribution ⚠️ Warning Commit 784ae80 used Claude Code (stated in message body) but lacks required Red Hat attribution trailers (Assisted-by or Generated-by) in formal Git footer format. Add proper Git trailer: Assisted-by: Claude Code <noreply@anthropic.com> to commit message footer section per Red Hat attribution standards.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: filtering unpublished catalog items from the public API across both cluster and compute instance servers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets, API keys, tokens, passwords, private keys, base64 strings >32 chars, or credential-bearing URLs found in any of the 6 modified files.
No-Weak-Crypto ✅ Passed No weak cryptography patterns detected. PR uses standard gRPC error handling and Google's cel-go for filter validation, not cryptographic operations.
No-Injection-Vectors ✅ Passed CEL syntax validation via google/cel-go parser prevents injection. Malformed filters rejected before string composition; test cases verify injection attempts like 'true) || (true' fail CEL parsing.
Container-Privileges ✅ Passed This PR modifies only Go source files (catalog validation and server logic); no container/K8s manifest changes present. Check is not applicable.
No-Sensitive-Data-In-Logs ✅ Passed No logging of sensitive data found. New code returns wrapped CEL parser errors, not raw filter strings. Existing logging unchanged and does not expose credentials or PII.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@internal/servers/catalog_item_validation.go`:
- Around line 43-48: The current addPublishedFilter function naively
concatenates strings which lets a user-supplied filter inject/reshape CEL logic
and bypass the appended "this.published == true"; fix by moving from string
concatenation to AST-level composition: parse the user filter into a CEL AST
(use the same CEL parser/AST utilities used by
internal/database/dao/filter_translator.go), construct a literal AST node for
this.published == true, and combine them with a new logical AND AST node so the
published predicate is always enforced; if AST composition is not possible right
away, implement strict validation of the parsed user AST against an allow-list
of permitted node kinds/fields/operators before accepting the filter to prevent
grouping/injection attacks.

In `@internal/servers/compute_instance_catalog_items_server_test.go`:
- Around line 254-260: In the "Get returns not found for unpublished object"
test, explicitly set Published: false on the ComputeInstanceCatalogItem builder
so the created item is unambiguously unpublished; update the call that builds
the object (publicv1.ComputeInstanceCatalogItem_builder) used in
server.Create(...) to include Published: false before calling Build() so the
test doesn't rely on implicit defaults.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: aab46044-5921-4ee7-ade9-cb68e64e713f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c20bff and 39043bf.

📒 Files selected for processing (6)
  • internal/servers/catalog_item_validation.go
  • internal/servers/catalog_item_validation_test.go
  • internal/servers/cluster_catalog_items_server.go
  • internal/servers/cluster_catalog_items_server_test.go
  • internal/servers/compute_instance_catalog_items_server.go
  • internal/servers/compute_instance_catalog_items_server_test.go

Comment thread internal/servers/catalog_item_validation.go Outdated
Comment thread internal/servers/compute_instance_catalog_items_server_test.go
@jhernand

jhernand commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

This means that tenant admins (which don't have access to the private API) will not be able to see the unpublished catalog items that they created: they will have to take note in a piece of paper of the identifier, because they will no longer be able to see them after changing the published flag to false. Is that the intent?

Comment thread internal/servers/catalog_item_validation.go Outdated
Comment thread internal/servers/catalog_item_validation.go Outdated
@tzvatot

tzvatot commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

This means that tenant admins (which don't have access to the private API) will not be able to see the unpublished catalog items that they created: they will have to take note in a piece of paper of the identifier, because they will no longer be able to see them after changing the published flag to false. Is that the intent?

Not the intent. Unpublished items should be visible to the user who created them.

Fixed by using creator-based visibility: the published filter is now this.published || this.metadata.creator == "<current_user>", so creators can see and manage their own unpublished items through the public API. The Get endpoint applies the same logic.

Global catalog items (tenant="shared") are managed via the private API, so this does not affect them.

tzvatot added 6 commits May 28, 2026 12:52
The public List and Get endpoints for both cluster and compute instance
catalog items now enforce published visibility. List injects a CEL
filter (this.published == true) and Get returns NotFound for unpublished
items. Private API endpoints remain unaffected.

Generated with [Claude Code](https://claude.com/claude-code)
- Add table-driven unit test for addPublishedFilter (both branches)
- Add tests for List with user filter combined with unpublished items
- Use explicit Published: false in unpublished test cases

Generated with [Claude Code](https://claude.com/claude-code)
Match the cluster catalog item test by explicitly setting Published: false
instead of relying on proto zero-value default.

Generated with [Claude Code](https://claude.com/claude-code)
- Simplify filter from `this.published == true` to `this.published`
- Move addPublishedFilter from package-level function to methods on
  ClusterCatalogItemsServer and ComputeInstanceCatalogItemsServer to
  avoid naming clashes in the servers package
- Fix CEL filter bypass: validate user filter is a syntactically valid
  CEL expression before composing, preventing injection like
  `true) || (true` from breaking out of parenthesized composition

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
- Use sync.Once to initialize CEL env once instead of per-call
- Add test case for valid filter with OR to confirm safe composition

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
Published filter now uses creator-based visibility: unpublished items
are visible to the user who created them via both List and Get.

List filter: (this.published || this.metadata.creator == '<user>')
Get: allow access if published or caller is the creator.

This ensures tenant admins who create catalog items through the public
API retain visibility after setting published to false.

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
@tzvatot
tzvatot force-pushed the OSAC-58/filter-unpublished-catalog-items branch from d1fd45d to 035ed85 Compare May 28, 2026 09:55
Comment thread internal/servers/catalog_item_validation.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@internal/servers/catalog_item_validation.go`:
- Around line 60-63: buildPublishedClause currently injects raw
auth.SubjectFromContext(ctx).User into a CEL single-quoted literal, allowing a
username with quotes/backslashes to break the predicate; fix by escaping
backslashes and single quotes before embedding. In buildPublishedClause, compute
an escaped username (escape "\" -> "\\" first, then "'" -> "\'") and use that
escaped value in the fmt.Sprintf for the metadata.creator comparison; ensure
strings is imported and leave the rest of addPublishedFilter unchanged so the
constructed publishedClause is safe to concatenate.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 4acb44c7-f11f-417c-b9f1-6fdab79d73e3

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb3bb0 and 035ed85.

📒 Files selected for processing (6)
  • internal/servers/catalog_item_validation.go
  • internal/servers/catalog_item_validation_test.go
  • internal/servers/cluster_catalog_items_server.go
  • internal/servers/cluster_catalog_items_server_test.go
  • internal/servers/compute_instance_catalog_items_server.go
  • internal/servers/compute_instance_catalog_items_server_test.go

Comment thread internal/servers/catalog_item_validation.go Outdated
…x tests

Revert creator-based unpublished item visibility per reviewer feedback:
unpublished items should be managed through the upcoming authorization
and projects system instead of CEL filter composition.

The published filter is now the simple `this.published` for all users.

Also add direct unit tests for validateCELSyntax and verify malformed
filter tests return InvalidArgument status code.

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
@adriengentil

adriengentil commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

does this change includes this exception:

Exception: a Tenant User can always Get a catalog item referenced by one of their existing CNAs, even if that item is unpublished.

?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@internal/servers/cluster_catalog_items_server.go`:
- Around line 270-278: The addPublishedFilter function duplicates
published-filter composition logic across catalog servers (e.g.,
ClusterCatalogItemsServer.addPublishedFilter and
ComputeInstanceCatalogItemsServer.addPublishedFilter); extract this logic into a
single shared helper (e.g., PublishFilterForCEL or ComposePublishedFilter) and
have both servers call that helper instead of keeping local copies; the helper
should accept the incoming filter string, call validateCELSyntax(filter) and
return either the default "this.published" or "(" + filter + ") &&
this.published" and propagate the same grpcstatus error on invalid syntax so
behavior remains identical across services.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b5768071-4787-49c0-950d-63997c136938

📥 Commits

Reviewing files that changed from the base of the PR and between 035ed85 and 784ae80.

📒 Files selected for processing (6)
  • internal/servers/catalog_item_validation.go
  • internal/servers/catalog_item_validation_test.go
  • internal/servers/cluster_catalog_items_server.go
  • internal/servers/cluster_catalog_items_server_test.go
  • internal/servers/compute_instance_catalog_items_server.go
  • internal/servers/compute_instance_catalog_items_server_test.go
💤 Files with no reviewable changes (1)
  • internal/servers/catalog_item_validation.go

Comment thread internal/servers/cluster_catalog_items_server.go
tzvatot added 2 commits May 31, 2026 11:13
… resource

Per the enhancement proposal, a user can always Get a catalog item
referenced by one of their existing clusters or compute instances,
even if that item is unpublished. This ensures users retain access
to catalog item details after an admin unpublishes it.

The check uses a tenant-scoped DAO query so users can only see
references from their own visible resources.

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
… resource

Per the enhancement proposal, a user can always Get a catalog item
referenced by one of their existing clusters or compute instances,
even if that item is unpublished.

Introduce catalogItemReferenceChecker interface with a DAO-backed
implementation (daoReferenceChecker) shared by both cluster and
compute instance catalog item servers. The DAO query is tenant-scoped
so users can only see references from their own visible resources.

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Elad Tabak <etabak@redhat.com>
@tzvatot

tzvatot commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

does this change includes this exception:

Exception: a Tenant User can always Get a catalog item referenced by one of their existing CNAs, even if that item is unpublished.

?

Yes, this is now implemented. A user can Get an unpublished catalog item if they have an existing cluster or compute instance that references it. The check uses a tenant-scoped query so it only considers resources visible to the caller. The List endpoint still filters unpublished items as before - only direct Get by ID has this exception, matching the enhancement proposal.

@openshift-ci

openshift-ci Bot commented Jun 1, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jhernand, tzvatot

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tzvatot

tzvatot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@openshift-merge-bot
openshift-merge-bot Bot merged commit f537d75 into osac-project:main Jun 1, 2026
13 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants