Skip to content

Enable GET /api/v3/individuals/{id} with proper view ACL#1633

Open
JasonWildMe wants to merge 1 commit into
mainfrom
fix/api-v3-individuals-get
Open

Enable GET /api/v3/individuals/{id} with proper view ACL#1633
JasonWildMe wants to merge 1 commit into
mainfrom
fix/api-v3-individuals-get

Conversation

@JasonWildMe

Copy link
Copy Markdown
Collaborator

Summary

Enables GET /api/v3/individuals/{individualID} to actually resolve and return a MarkedIndividual. The URL pattern was already mapped to the BaseObject servlet in web.xml, and MarkedIndividual already overrides Base.getById(...), but two pieces were missing, so the endpoint returned 404 (and would have returned 401 even once found):

  1. Base.getByClassnameAndId(...) — the dispatch switch used by BaseObject.processGet(...) — handled only encounters and annotations, so an individual GET fell through to return null404 not found.
  2. MarkedIndividual did not override canUserView(User, Shepherd). Base.jsonForApiGet(...) gates on canUserView(...), and the Base default returns false, so the endpoint would have returned 401 for every caller even after the object resolved.

Why

The supported way to fetch a single individual as JSON via the modern API is this /api/v3 endpoint, which returns the sanitized OpenSearch-document representation and enforces canUserView(...). With the wiring incomplete, there was no working /api/v3 route to retrieve an individual by its individualID, leaving only the generic DataNucleus REST endpoint — which recursively serializes the full object graph and is unsuitable for entities with bidirectional relationships.

Changes

  • Base.getByClassnameAndId — add the individuals case:
    case "individuals":
        tmp = new MarkedIndividual();
        break;
  • MarkedIndividual.canUserView(User, Shepherd) — new override. A user may view an individual if they are an admin or can view at least one of its encounters. An individual with no encounters grants no view access (it is not made visible to any logged-in user). This mirrors Encounter.canUserView(...) and the per-encounter access loop already used for occurrences.
  • BaseObject.processGet — require a non-empty id segment before lookup, so /api/v3/individuals (no id) returns the existing 404 path rather than throwing and falling through to a generic 500.

Behavior

MarkedIndividual.getById()Shepherd.getMarkedIndividual(id) resolves by the individualID primary key. The response flows through Base.jsonForApiGet(...):

  • permitted user → sanitized individual JSON, statusCode 200
  • user without view access → statusCode 401
  • unknown / missing id → statusCode 404

Scope note

/api/v3/occurrences/* is also mapped-but-unresolved by the same switch, but is intentionally not enabled here: the occurrence view path (Collaboration.canUserViewOccurrence) currently treats a zero-encounter occurrence as viewable by any authenticated user, which warrants its own review before that route is exposed.

Testing

  • mvn compile — clean.

🤖 Generated with Claude Code

The /api/v3/individuals/* path was already mapped to the BaseObject
servlet and MarkedIndividual already overrode getById(), but the
endpoint never worked:

- Base.getByClassnameAndId() (the dispatch used by processGet) handled
  only encounters and annotations, so an individual GET fell through to
  return null -> 404.
- MarkedIndividual did not override canUserView(User, Shepherd), so
  Base.jsonForApiGet()'s ACL gate used the Base default (false) and would
  have returned 401 for every caller even once the object resolved.

Add the individuals dispatch case, add a MarkedIndividual.canUserView()
that grants access to admins or users who can view at least one of the
individual's encounters (no view grant for an encounter-less individual),
and guard processGet against a missing id segment so /api/v3/individuals
returns 404 rather than a generic 500.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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