Skip to content

Allow global source read permission to fetch a patient profile - #2538

Merged
p-hoffmann merged 1 commit into
webapi-3.0from
p-hoffmann/fix-206-person-profile-source-access
Aug 14, 2026
Merged

Allow global source read permission to fetch a patient profile#2538
p-hoffmann merged 1 commit into
webapi-3.0from
p-hoffmann/fix-206-person-profile-source-access

Conversation

@p-hoffmann

@p-hoffmann p-hoffmann commented Aug 14, 2026

Copy link
Copy Markdown
Member

Problem

Reported as OHDSI/Atlas3#206. After generating a sample of a generated cohort, the person list renders, but opening any person's profile returns access denied. The reporting user has global read and write permission on sources.

Cause

AuthorizationService.hasSourceAccess resolves the source key to an id and delegates to hasEntityAccess. Its SOURCE branch reads only the per-source ACL map:

case SOURCE -> {
  Set<AccessType> granted = authz.sourceAccess.get(entityId);
  yield granted != null && EntityGrant.hasAccess(accessType, granted);
}

It never consults the global read:source / write:source entitlement. Every other source-scoped read endpoint therefore pairs it with an isAnyPermitted check:

@PreAuthorize("isAnyPermitted(anyOf('read:source','write:source')) or hasSourceAccess(#sourceKey, READ)")

69 read endpoints use that paired form. PersonService.getPersonProfile was the only one in production code carrying the ACL half on its own, so a user entitled globally but without a per-source ACL row is refused there and nowhere else. The only other bare uses are inside the SourceAccessIT test fixture, where they are deliberate. It has been that way since the endpoint was introduced in "Migrated Person service for patient profiles" (#2506), so it reads as an oversight in the migration rather than an intentional tightening.

The Atlas3 client performs no permission check of its own before opening a profile, so this response is what the user sees.

Fix

Use the same paired expression as the other read endpoints. After the change no source-scoped read endpoint in production code carries the ACL-only form.

A user with global read:source or write:source permission, but no
per-source access-control entry, was denied the patient profile endpoint
while every other source-scoped read endpoint let them through. That is
what makes the cohort sample list viewable but the profile behind it
return access denied.

hasSourceAccess resolves only per-entity ACL entries: it calls
hasEntityAccess for the source and never consults the global
read:source/write:source entitlement. Every other source-scoped read in
the codebase therefore pairs it with an isAnyPermitted check, 55
endpoints in all. getPersonProfile was the single exception, carrying
only the ACL half since it was first added in the person service
migration.

Use the same paired expression as its siblings.

Reported in OHDSI/Atlas3#206.
@p-hoffmann
p-hoffmann merged commit 5b71f69 into webapi-3.0 Aug 14, 2026
6 checks passed
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