Skip to content

Feature/DPAV 3018 - #68

Open
Filip-sz-informed wants to merge 8 commits into
developfrom
feature/DPAV-3018
Open

Feature/DPAV 3018#68
Filip-sz-informed wants to merge 8 commits into
developfrom
feature/DPAV-3018

Conversation

@Filip-sz-informed

@Filip-sz-informed Filip-sz-informed commented Aug 31, 2026

Copy link
Copy Markdown

Sensitive Credential Checks

  • As the author of these changes, I have checked for any sensitive credentials prior to this review being requested.
  • As a reviewer of these changes, I have checked for any sensitive credentials prior to approving this merge.

Motivation and Context

DPAV-3018: consumers currently have no API to discover which data products they are authorised to see. DPAV-3017 delivered a generic Policy Enforcement Point (PEP) that enforces a single allow/deny decision per request, but nothing yet applies policy to a set of resources or exposes product search. This adds a discovery endpoint that returns only the products the requester is authorised to see, per the applicable PDP policy, without leaking existence or metadata of restricted products.

Description

  • Add POST /api/v1/product/discovery: accepts requester-supplied search criteria (name/topic/type, all optional), returns only products the requester is authorised to discover, gated by a new discover_products client role.
  • Add an org-unscoped, bounded candidate query (ProductRepository.findDiscoveryCandidates + ProductService/ProductServiceImpl) — policy, not org membership, decides visibility, so candidates aren't pre-filtered by the requester's own organisation; application.product-discovery.max-candidates (default 200) bounds the per-request PDP call count.
  • Add ProductDiscoveryService, which queries candidates then evaluates one PDP decision per candidate via the existing PolicyDecisionClient/PolicyInput (reused unmodified from DPAV-3017), keeping only ALLOWed products. resource/action are repurposed as product:{id}/discover rather than URI/HTTP-method — PolicyInput's javadoc was generalised to describe them as caller-defined, opaque strings.
  • Fail-closed per candidate, not per request: a PDP denial or PDP failure excludes just that candidate, so a partial PDP outage degrades results instead of failing the whole request; no products authorised or matching filters returns 200 with an empty list, not an error.
  • Not wired through PolicyEnforcementInterceptor — that interceptor makes one whole-request decision and is registered only for /api/v1/configuration/**; discovery needs N per-candidate decisions, so ProductDiscoveryService calls PolicyDecisionClient directly.
  • Incidental fix: GlobalExceptionHandler's generic Exception handler was silently turning @Valid request-body failures into a 500 instead of 400 (also affects CertificateController's bootstrap endpoint) — added dedicated MethodArgumentNotValidException/HttpMessageNotReadableException handlers.
  • Incidental: widened RequestRejectionSupport.getOrganisationId to public so the new controller can reuse the organisation lookup CertificateValidationInterceptor already resolved, instead of duplicating it.
  • Add a starting discover-action rule to docker/opa/policy.rego for local/dev PDP testing; document the new discover_products role in docs/AUTHENTICATION_REQUIREMENTS.md.

How Has This Been Tested?

  • New unit tests: ProductDiscoveryDtoTest (request/response DTO validation and serialization), ProductDiscoveryServiceImplTest (fully permitted / partially permitted / none-authorised PDP filtering, PolicyInput shape, candidate-query-then-filter orchestration), ProductServiceImplTest additions (candidate-query filter/limit delegation).
  • New integration test: ProductDiscoveryControllerTest (MockMvc, following ConfigurationPolicyEnforcementIntegrationTest's pattern) covering fully permitted, partially filtered, no candidates, no authorised products, a search filter matching a PDP-denied product (still excluded — filters can't widen authorisation), invalid body (400), malformed JSON (400), and empty body treated as no filter.
  • Full suite green: mvn clean verify (297 tests, JaCoCo coverage gate met, spotless-clean).
  • Not covered: no @DataJpaTest/real-DB repository test exists in this project (Flyway/ddl-auto are disabled in the test datasource, and enabling Flyway against H2 fails on a pre-existing, unrelated migration issue — an unquoted value column, an H2 reserved word) — the candidate query is verified via the service-layer unit test plus code review instead. Manual verification of the OPA rego rule against a running local OPA instance is left as a local/dev follow-up (no OPA/Docker available in the environment this was built in).

Screenshots (if appropriate):

Checklist:

  • It contains only changes required by issue (does not contain other PR)
  • Includes link to an issue (if apply) — DPAV-3018
  • I have added tests to cover my changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DpPNCW5LYqyGEbsq8j3F5S

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

✅ OSS Checks Passed

All tracked OSS checks passed in this run.

📊 Total Files 🟢 Passed 🔴 Failed 🧮 Score
13 13 0 100%

Results from commit d7141d9, view the full job summary↗️ for detailed results.

♻️ This comment has been updated with latest results.

@Filip-sz-informed Filip-sz-informed changed the title Policy-aware product discovery (DPAV-3018) Feature/DPAV 3018 Aug 31, 2026
@Valid request-body validation failures and malformed JSON bodies were
falling through to GlobalExceptionHandler's generic Exception handler,
which returns 500. Add dedicated MethodArgumentNotValidException and
HttpMessageNotReadableException handlers so both cases return 400, as
already relied on by CertificateController's bootstrap endpoint and the
new product discovery endpoint.
Widen RequestRejectionSupport (and getOrganisationId) to public so the
new product discovery controller, in a different package, can read the
organisation CertificateValidationInterceptor already resolved for the
request instead of duplicating the request-attribute lookup.
Implements DPAV-3018: POST /api/v1/product/discovery returns only the
products the authenticated requester is authorised to see. Search
criteria (name/topic/type, all optional) narrow the org-unscoped
candidate query, then ProductDiscoveryService evaluates one PDP
decision per candidate via the existing PolicyDecisionClient (built for
DPAV-3017's PEP), keeping only ALLOWed products - a PDP denial or
failure excludes just that candidate rather than the whole request.

- ProductRepository.findDiscoveryCandidates: bounded, filtered
  candidate query across all organisations (policy decides visibility,
  not org membership)
- ProductDiscoveryService: queries candidates then filters by policy;
  new `discover_products` role gates the endpoint
- docker/opa/policy.rego: starting example for a discover-action rule
- application.yml: application.product-discovery.max-candidates bounds
  the per-request PDP call count
noAuthorisedProducts_returnsEmptyListNotError was byte-identical to
noCandidates_returnsEmptyListNotError (Sonar). Give it distinct value:
send search criteria and verify they're passed through to
ProductDiscoveryService.discover unchanged, instead of repeating the
same empty-body/empty-response assertion.
evaluate() takes a single argument, so wrapping it in eq(...) is a
no-op Mockito already does by default (Sonar). Pass the PolicyInput
value directly.
The MethodArgumentNotValidException/HttpMessageNotReadableException
handlers logged request.getContextPath() (empty for a root-mapped app)
instead of request.getDescription(false), unlike every sibling handler
in this class - leaving debug logs with no indication of which
endpoint a validation failure came from.
application.product-discovery.max-candidates fed straight into
PageRequest.of(0, n), which throws IllegalArgumentException for n < 1
(e.g. a misconfigured 0, or an attempt to mean "unlimited") - crashing
every discovery request with a 500 instead of failing at startup.
Validate in the constructor, matching the sibling OpaProperties'
fail-fast intent.
findDiscoveryCandidates built its %contains% pattern via CONCAT without
escaping the SQL LIKE metacharacters % and _ in the caller-supplied
value, so e.g. name=Data_Feed also matched DataXFeed (since '_' is the
LIKE single-char wildcard) - silently violating the documented
"contains" filter contract for any name/topic containing % or _.
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

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.

1 participant