Skip to content

feat: management REST API for projects, profiles, forward nodes, auth configs, and monitoring - #297

Open
jbardet wants to merge 3 commits into
nroduit:masterfrom
jbardet:port/rest-api-2.0
Open

feat: management REST API for projects, profiles, forward nodes, auth configs, and monitoring#297
jbardet wants to merge 3 commits into
nroduit:masterfrom
jbardet:port/rest-api-2.0

Conversation

@jbardet

@jbardet jbardet commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What's in this PR

Five controllers under org.karnak.backend.controller: ProjectController, ProfileController, ForwardNodeController, MonitoringController, AuthConfigController, plus API.md as the endpoint reference. Auth reuses the existing Spring Security setup as-is, no new auth mechanism.

Adapting this onto current master needed real changes beyond a straight port:

  • HTTP Basic wasn't enabled under the new security chain. VaadinSecurityConfigurer wires up its own cookie/form login but doesn't enable Basic auth on its own. Added .httpBasic(Customizer.withDefaults()) to SecurityInMemoryConfig (OIDC mode untouched: Basic auth was never part of that path).
  • Monitoring was reworked upstream into an aggregated per-series model (TransferSeriesStatusEntity, no more per-instance rows) with no pageable/count methods. Added two small service methods reusing the specification-based query the CSV export already had, rather than reintroducing the old per-instance model. Status values changed (SENT/NOT_SENT/EXCLUDED/ERROR replacing SUCCESS/WARNING/ERROR); sopInstanceUid/quoteCharacter no longer apply and were dropped from the API and docs.
  • Entity annotations moved to fields. Several classes now use Lombok @Getter/@Setter at the field level instead of explicit methods, so @JsonIgnore placement (avoiding circular JSON refs and leaking SecretEntity.secretKey/AuthConfigEntity.clientSecret) moved onto fields too.
  • Dropped an unused SecretService field found in the original ProjectController (dead code, never called).
  • Replaced commons-codec (a transitive dependency that no longer resolves on master) with the JDK's built-in HexFormat.
  • Added true to the compiler plugin, required for the controllers' unqualified @PathVariable/@RequestParam names to resolve.
  • Found and fixed a real bug while porting ProjectService.remove(): deleting a project still referenced by a destination (de-identification or tag-morphing) would violate the DB's FK constraint. Reused upstream's own findByTagMorphingProjectEntity (added upstream for an unrelated notification feature) rather than adding a duplicate query.

Bugs found and fixed via live end-to-end testing

Since this PR touches the security chain, I didn't want to ship it validated only by MockMvcBuilders.standaloneSetup() (which never exercises the real filter chain), so I ran it against a real downstream pipeline as an actual scripted client would use it: auth, config apply, then driving DICOM through a shift_from_api-configured profile. That surfaced three real bugs, all fixed in this branch:

  1. /api/** had no explicit authorization rule. Requests fell through to VaadinSecurityConfigurer's route-based access control, which denies (403) any route it doesn't recognize as a Vaadin view, even for an authenticated user, who should just get through. Fixed with an explicit .requestMatchers("/api/**").authenticated() rule, in both SecurityInMemoryConfig and SecurityConfiguration (OIDC).
  2. CSRF blocked every POST/PUT/DELETE to /api/. Spring Security's default CSRF protection rejected every mutating call from a non-browser Basic-auth client (no CSRF token available), which would have made the API's actual write operations, the whole point of it, unusable. Fixed by exempting /api/ from CSRF, the conventional treatment for a stateless REST API.
  3. Infinite JSON serialization recursion in four entities. ArgumentEntity, TagEntity (parent of IncludedTagEntity/ExcludedTagEntity), ProfileEntity.projectEntities, and KheopsAlbumsEntity.destinationEntity are bidirectional JPA back-references that were missing @JsonIgnore. Since the forward side of each relationship is already reachable from a REST response (profile elements, tags/arguments, project↔profile, destination↔kheopsAlbums), Jackson recursed through the cycle indefinitely, and GET /api/projects / GET /api/forward-nodes returned truncated/invalid JSON instead of a 200. Fixed by adding @JsonIgnore to each back-reference.

Testing

Built and tested in a maven:3.9-eclipse-temurin-25 container against current master:

  • 1550/1551 backend tests pass. The one failure (ForwardServiceTest.web_transfer_other_conflict_409_is_treated_as_already_present) is pre-existing on a clean, untouched master checkout, unrelated to this branch, and already fixed separately in test: fix stale sent() assertion for a 409-duplicate STOW-RS outcome #296.
  • spring-javaformat:validate clean.
  • Live-verified against a real deployment: GET /api/** returns 200/204 instead of 403 for an authenticated caller; POST to /api/profiles and /api/projects returns normal validation responses instead of 403; GET /api/projects returns clean, complete JSON (previously truncated at ~14KB of recursive nesting); a shift_from_api-configured profile applied through the API is picked up correctly by the DICOM listener.

Scope

Kept the Python client (python-client/) and setup_deid_gateway.py as a separate second commit on the same branch (all of its own tests pass), so it can be split into its own PR later if you'd prefer Java-only first.

jbardet added 3 commits July 20, 2026 09:09
… configs, and monitoring

Adds a programmatic configuration surface alongside the Vaadin UI:
ProjectController, ProfileController, ForwardNodeController,
AuthConfigController, MonitoringController. Auth reuses the existing
Spring Security setup (HTTP Basic added to the in-memory IdP chain
alongside the existing form login; OIDC unchanged).

Also fixes a pre-existing bug surfaced while adding project deletion
via the API: ProjectService.remove() left dangling FKs on destinations
that referenced the deleted project (de-identification or tag
morphing), which would violate the database constraint on delete.

Monitoring endpoints are adapted to the current aggregated per-series
model (TransferSeriesStatusEntity) via the specification-based
pagination/count TransferSeriesStatusRepo already provides.

Ported from jbardet/karnak (1.1.1-based fork) onto current master.
API.md documents every endpoint added in the previous commit (updated
for the current aggregated per-series monitoring model: status values
ALL/SENT/NOT_SENT/EXCLUDED/ERROR, no sopInstanceUid/quoteCharacter).

karnak-api-client (python-client/) and setup_deid_gateway.py are an
optional companion, not core: a thin authenticated HTTP client plus an
idempotent desired-state apply_config() from a JSON document. Kept as
a separate commit so it can be split into its own PR if preferred.

Ported from jbardet/karnak (1.1.1-based fork) onto current master.
Found by driving the ported REST API through a real downstream pipeline
before opening the upstream PR:

- /api/** had no explicit authorization rule, so authenticated requests
  fell through to VaadinSecurityConfigurer's route-based access control,
  which denies (403) any route it doesn't recognize as a Vaadin view.
  Add an explicit requestMatchers("/api/**").authenticated() rule in
  both SecurityInMemoryConfig and SecurityConfiguration (OIDC).

- Spring Security's default CSRF protection rejected every POST/PUT/
  DELETE to /api/** from non-browser Basic-auth clients (no CSRF token
  available), making the API's actual write operations unusable for its
  intended scripted/automated callers. Exempt /api/** from CSRF, the
  conventional treatment for a stateless REST API.

- ArgumentEntity.profileElementEntity, TagEntity.profileElementEntity,
  ProfileEntity.projectEntities, and KheopsAlbumsEntity.destinationEntity
  are bidirectional JPA back-references that were missing @JsonIgnore.
  Since the forward side of each relationship is already reachable from
  a REST response (profile elements, tags/arguments, project<->profile,
  destination<->kheopsAlbums), Jackson recursed through the cycle
  indefinitely, producing truncated/invalid JSON on GET /api/projects
  and GET /api/forward-nodes instead of a 200 response.

Verified live against the ported build: GET now returns 200/204 instead
of 403, POST returns normal validation responses instead of 403, and
GET /api/projects returns clean, complete JSON instead of a truncated
multi-hundred-level-deep recursive body.
@sonarqubecloud

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