Fix legacy user accounts with email-as-UUID blocking project membership (#1545)#1546
Open
JasonWildMe wants to merge 2 commits into
Open
Fix legacy user accounts with email-as-UUID blocking project membership (#1545)#1546JasonWildMe wants to merge 2 commits into
JasonWildMe wants to merge 2 commits into
Conversation
…ip (#1545) UserCreate was calling new User(email), which resolves to User(String uuid) and stored the email as the account's UUID primary key. Those accounts' getUUID() returns an email, so the project autocomplete posts an email-shaped id; ProjectUpdate/ProjectCreate then fall through to a username lookup that fails, and silently report success. - UserCreate: use new User(email, Util.generateUUID()) to stop creating more broken rows. - User(String uuid): @deprecated — callers have misused it historically. - ProjectUpdate/ProjectCreate: add a shared resolveUser() with an email-address fallback for legacy accounts, log every fallback hit, and surface unresolvedUserIds in the response so failures are visible. Does not migrate existing bad rows — that follows in a separate ticket with a JSP-driven, admin-auth'd, dry-run-capable data migration that also reindexes affected encounter/occurrence docs in OpenSearch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review on #1546 flagged a real bug: a non-UUID-shaped identifier went to getUser(username) before the email fallback, so a legacy email-as-UUID account could be mis-resolved to a different user whose username happens to equal that email. Put getUserByUUID first regardless of string shape — JDO application identity does a literal PK lookup, so it resolves real UUIDs AND email-as-UUID rows correctly, without risking the wrong match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1546 +/- ##
========================================
Coverage 51.65% 51.65%
========================================
Files 281 281
Lines 10309 10309
Branches 3195 3095 -100
========================================
Hits 5325 5325
- Misses 4714 4720 +6
+ Partials 270 264 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
JasonWildMe
added a commit
that referenced
this pull request
Apr 16, 2026
Codex review on #1546 flagged a real bug: a non-UUID-shaped identifier went to getUser(username) before the email fallback, so a legacy email-as-UUID account could be mis-resolved to a different user whose username happens to equal that email. Put getUserByUUID first regardless of string shape — JDO application identity does a literal PK lookup, so it resolves real UUIDs AND email-as-UUID rows correctly, without risking the wrong match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1545.
Root cause
UserCreate.java:103was callingnew User(email), which resolves to theUser(String uuid)constructor and stores the email address in theuuidfield (which is the JDO primary key perpackage.jdo:787). A small number of accounts created via this path haveuser.getUUID()returning an email.Downstream:
UserGetSimpleJSONservesu.getUUID()as the autocompleteid.editProject.jsp/createProject.jsppush that email intouserIdsToAdd.ProjectUpdate.addOrRemoveUsersFromProject/ProjectCreatedoif (Util.isUUID(userId)) getUserByUUID else getUser(username). The email isn't a UUID, so it falls to the username lookup, which misses if username ≠ email.res.success = trueis still set. UI shows "Success updating project data." The user is never added.Changes
UserCreate.java: switch tonew User(email, Util.generateUUID())so no new accounts are created with email-as-UUID.User.java:@Deprecatedon the single-argUser(String uuid)constructor with a Javadoc note pointing at this issue. Not removed — there are three legitimate callers (SpotterConserveIO,BulkImporter,StandardImport) that pass real UUIDs. Those will be migrated in follow-up tickets.ProjectUpdate.java: factor the user lookup into a sharedresolveUser()that adds angetUserByEmailAddressfallback for legacy accounts. Every fallback hit is logged. SurfaceunresolvedUserIds: [...]in the response JSON so silent misses are visible to the frontend.ProjectCreate.java: use the sameresolveUser()helper; sameunresolvedUserIdssurface. The bug was live in this path too.What this does NOT do
This is a code patch, not a data migration. Existing rows with email-as-UUID remain in the DB. The
resolveUser()fallback keeps those users functional for project add/remove until a proper migration runs.The migration is deliberately deferred to a separate ticket. It needs:
USERS.usernameis unique so it needs careful sequencing)Project.users,Project.ownerId(plain string),Organization.members,Encounter.submitters/photographers/informOthers,Occurrence.submitters/informOthers,ImportTask.creator,Collaboration, audit trailsencounterandoccurrencedocs (submitterUserIdandviewUsersare stored as the raw id) — not justOpenSearch.setPermissionsNeededDiagnostic SQL
Test plan
grep "resolved userId=.*via email-address fallback" catalina.outunresolvedUserIds.USERS.UUIDis a proper UUID.unresolvedUserIds.