Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/org/ecocean/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ public User(String email, String uuid) {
this.lastLogin = -1;
}

/**
* @deprecated GH-1545: Callers have historically misused this constructor by passing
* an email address where a UUID is expected, producing accounts whose primary key
* is the email. Prefer {@code new User(email, Util.generateUUID())} or another
* constructor when you do not already have a real UUID.
*/
@Deprecated
public User(String uuid) {
this.uuid = uuid;
setReceiveEmails(false);
Expand Down
19 changes: 13 additions & 6 deletions src/main/java/org/ecocean/servlet/ProjectCreate.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,24 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
}
}
if (projectUserIds != null && projectUserIds.length() > 0) {
JSONArray unresolved = res.optJSONArray("unresolvedUserIds");
if (unresolved == null) {
unresolved = new JSONArray();
res.put("unresolvedUserIds", unresolved);
}
for (int i = 0; i < projectUserIds.length(); i++) {
String userIdentifier = projectUserIds.getString(i);
if (!Util.stringIsEmptyOrNull(userIdentifier)) {
User user = null;
if (Util.isUUID(userIdentifier)) {
user = myShepherd.getUserByUUID(userIdentifier);
} else {
user = myShepherd.getUser(userIdentifier);
}
// GH-1545: use shared resolver with email-address fallback for
// legacy accounts whose UUID primary key stores an email.
User user = ProjectUpdate.resolveUser(myShepherd, userIdentifier);
if (user != null) {
newProject.addUser(user);
} else {
unresolved.put(userIdentifier);
System.out.println(
"ProjectCreate: could not resolve userIdentifier=" +
userIdentifier);
}
}
}
Expand Down
48 changes: 42 additions & 6 deletions src/main/java/org/ecocean/servlet/ProjectUpdate.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,27 +187,63 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)

private void addOrRemoveUsersFromProject(Project project, Shepherd myShepherd,
JSONArray usersToAddJSONArr, String action, JSONObject res) {
JSONArray unresolved = res.optJSONArray("unresolvedUserIds");
if (unresolved == null) {
unresolved = new JSONArray();
res.put("unresolvedUserIds", unresolved);
}
for (int i = 0; i < usersToAddJSONArr.length(); i++) {
String userId = usersToAddJSONArr.getString(i);
User user = null;
if (Util.isUUID(userId)) {
user = myShepherd.getUserByUUID(userId);
} else {
user = myShepherd.getUser(userId);
}
User user = resolveUser(myShepherd, userId);
if (user != null && !StringUtils.isNullOrEmpty(action)) {
if ("add".equals(action)) {
project.addUser(user);
} else if ("remove".equals(action)) {
project.removeUser(user);
}
} else {
unresolved.put(userId);
System.out.println(
"ProjectUpdate.addOrRemoveUsersFromProject: could not resolve userId=" +
userId + " for action=" + action + " project=" + project.getId());
}
}
myShepherd.updateDBTransaction();
res.put("modified", true);
res.put("success", true);
}

// GH-1545: Resolve a user from an autocomplete-supplied identifier.
//
// Ordering matters. Some legacy User accounts have an email stored in their UUID
// primary key (see UserCreate.java history and the deprecated User(String uuid)
// constructor). For those rows, the frontend posts `u.getId()` which is the email.
//
// We therefore try the PK lookup FIRST, not last — and regardless of whether the
// string looks UUID-shaped — because:
// (1) for real UUIDs it's the direct, correct path; and
// (2) for legacy email-as-UUID rows it still resolves via the literal PK
// ("alice@example.org" IS that user's primary key), which avoids a wrong
// match if some other account happens to have that email as their username.
//
// Only after the PK miss do we try username and then email-address lookups.
// Every email-fallback hit is logged so operators can gauge the migration backlog.
static User resolveUser(Shepherd myShepherd, String userId) {
if (StringUtils.isNullOrEmpty(userId)) return null;
User user = myShepherd.getUserByUUID(userId);
if (user != null) return user;
user = myShepherd.getUser(userId);
if (user != null) return user;
user = myShepherd.getUserByEmailAddress(userId);
if (user != null) {
System.out.println(
"ProjectUpdate.resolveUser: resolved userId=" + userId +
" via email-address fallback (GH-1545 legacy uuid). user.uuid=" +
user.getUUID());
}
return user;
}

private JSONArray removeUnauthorizedEncounters(JSONArray encountersToAddJSONArr,
Shepherd myShepherd, HttpServletRequest request) {
JSONArray filteredResults = new JSONArray();
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/ecocean/servlet/UserCreate.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
salt);
newUser = new User(username, hashedPassword, salt);
} else {
newUser = new User(email);
// GH-1545: use the (email, uuid) ctor so the email is not stored as the UUID.
// The single-arg User(String uuid) ctor was misused here, producing
// accounts whose primary key is the email address.
newUser = new User(email, Util.generateUUID());
}
myShepherd.getPM().makePersistent(newUser);
createThisUser = true;
Expand Down
Loading