Skip to content

fix: LazyInitializationException in PAT authentication on non-OSIV endpoints [DHIS2-21786]#24417

Open
netroms wants to merge 3 commits into
masterfrom
fix/DHIS2-21786-pat-auth-lazy-init
Open

fix: LazyInitializationException in PAT authentication on non-OSIV endpoints [DHIS2-21786]#24417
netroms wants to merge 3 commits into
masterfrom
fix/DHIS2-21786-pat-auth-lazy-init

Conversation

@netroms

@netroms netroms commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Jira: https://dhis2.atlassian.net/browse/DHIS2-21786

Same-repo rehost of #24407 so CI (including SonarQube) runs with repository secrets. Original work by @torbjorn from fork torbjorn/dhis2-core.

Problem

Authenticating with a personal access token on an endpoint excluded from open-session-in-view (e.g. /api/dataValueSets, excluded since #23127) intermittently fails with HTTP 500:

org.hibernate.LazyInitializationException: failed to lazily initialize a
collection of role: org.hisp.dhis.user.User.userRoles, could not initialize
proxy - no Session

It happens when the token is not in the apiTokenCache (first use, TTL expiry, or eviction), which makes it intermittent in production: whichever request happens to re-authenticate on a non-OSIV endpoint gets the 500. Reproduced against current master: the first request with a fresh PAT against /api/dataValueSets returns 500 with the exception above. Affects 2.43.0+ and master.

ApiTokenAuthManager.validateAndCreateUserDetails looks up the token owner via userStore.getUserByUsername and builds UserDetails with no transactional boundary. On non-OSIV endpoints the store returns a detached entity, and UserDetails.createUserDetails walking user.getAuthorities() / user.getUserRoles() throws.

Fix

Same pattern as #23611 (the JWT bearer equivalent of this bug): create the UserDetails via UserService.createUserDetailsByUsername, which is @Transactional(readOnly = true), so lookup and lazy-collection access share one session. The PAT-specific validation (2FA/disabled/locked/expired) now reads the flags off the returned UserDetails; DefaultUserService.createUserDetails computes the same flags the manager previously computed itself, and loads the same org-unit UID sets, so behavior is unchanged. The now-unused UserStore and OrganisationUnitService dependencies are removed.

Related: #23271 ("avoid lazy init on PAT first boot") sidestepped this for /api/system/ping by making it unauthenticated; this PR fixes the underlying PAT path.

Regression test

ApiTokenAuthTest (e2e, modeled on the JWT regression test in OAuth2Test from #23172): creates a user and a PAT, then issues the first request with that token (cold cache) against /api/dataValueSets. Verified against dhis2/core-dev images: fails with the 500 above on unfixed master, passes with this fix (409 from the endpoint's parameter validation, i.e. authentication succeeded). Note: needs the e2e compose setup with an empty database (dhis-2/dhis-test-e2e/docker-compose.yml) — the helpers assume the bootstrap-seeded Superuser role.

Notes

Test plan

  • CI green including SonarQube analysis
  • e2e ApiTokenAuthTest passes
  • Spot-check PAT against a non-OSIV endpoint (e.g. /api/dataValueSets) with cold token cache

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

3 participants