From c4a195825edc0c74c6ec9c3069bede2fe24062aa Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 16 Mar 2026 09:57:23 -0700 Subject: [PATCH 1/2] Delete avatar when deleting a user --- .../org/labkey/api/security/UserManager.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/api/src/org/labkey/api/security/UserManager.java b/api/src/org/labkey/api/security/UserManager.java index 2d3aa600e01..59f664a3a2e 100644 --- a/api/src/org/labkey/api/security/UserManager.java +++ b/api/src/org/labkey/api/security/UserManager.java @@ -63,6 +63,9 @@ import org.labkey.api.security.permissions.SiteAdminPermission; import org.labkey.api.security.roles.ApplicationAdminRole; import org.labkey.api.security.roles.SiteAdminRole; +import org.labkey.api.thumbnail.ThumbnailProvider; +import org.labkey.api.thumbnail.ThumbnailService; +import org.labkey.api.thumbnail.ThumbnailService.ImageType; import org.labkey.api.util.HeartBeat; import org.labkey.api.util.HtmlString; import org.labkey.api.util.LinkBuilder; @@ -455,7 +458,7 @@ public static Date getMostRecentLogin() Aggregate maxLoginValue = new Aggregate(createdFk, Aggregate.BaseType.MAX, null, true); TableSelector logins = getRecentLoginOrOuts(LoggedInOrOut.in, null, uat, Collections.singleton(createdCol)); - Aggregate.Result result = logins.getAggregates(Collections.singletonList(maxLoginValue)).get(createdCol.getName()).get(0); + Aggregate.Result result = logins.getAggregates(Collections.singletonList(maxLoginValue)).get(createdCol.getName()).getFirst(); return (Date) result.getValue(); } @@ -472,7 +475,7 @@ public static int getActiveDaysCount(Date since) Aggregate countDistinctDates = new Aggregate(datePartCol.getFieldKey(), Aggregate.BaseType.COUNT, null, true); TableSelector logins = getRecentLoginOrOuts(LoggedInOrOut.in, since, uat, Collections.singleton(datePartCol)); - Aggregate.Result result = logins.getAggregates(Collections.singletonList(countDistinctDates)).get(datePartCol.getName()).get(0); + Aggregate.Result result = logins.getAggregates(Collections.singletonList(countDistinctDates)).get(datePartCol.getName()).getFirst(); return Math.toIntExact((long) result.getValue()); } @@ -502,7 +505,6 @@ public static int getAuthCount(@Nullable Date since, boolean excludeSystemUsers, sql.append(" AND uat.Comment LIKE "); sql.appendStringLiteral("%" + UserAuditEvent.LOGGED_IN + "%", uat.getSqlDialect()); - if (apiKeyOnly) { sql.append(" AND uat.Comment LIKE "); @@ -908,17 +910,17 @@ public static void auditBadVerificationToken(int userId, String oldEmail, String public static void deleteUser(int userId) throws UserManagementException { - User deletUser = getUser(userId); - if (null == deletUser) + User deleteUser = getUser(userId); + if (null == deleteUser) return; - removeRecentUser(deletUser); + removeRecentUser(deleteUser); - List errors = fireDeleteUser(deletUser); + List errors = fireDeleteUser(deleteUser); if (!errors.isEmpty()) { - Throwable first = errors.get(0); + Throwable first = errors.getFirst(); if (first instanceof RuntimeException) throw (RuntimeException)first; else @@ -927,19 +929,25 @@ public static void deleteUser(int userId) throws UserManagementException try (Transaction transaction = CORE.getScope().ensureTransaction()) { - boolean needToEnsureRootAdmins = SecurityManager.isRootAdmin(deletUser); + boolean needToEnsureRootAdmins = SecurityManager.isRootAdmin(deleteUser); SqlExecutor executor = new SqlExecutor(CORE.getSchema()); executor.execute("DELETE FROM " + CORE.getTableInfoRoleAssignments() + " WHERE UserId=?", userId); executor.execute("DELETE FROM " + CORE.getTableInfoMembers() + " WHERE UserId=?", userId); - addToUserHistory(deletUser, deletUser.getEmail() + " was deleted from the system"); + addToUserHistory(deleteUser, deleteUser.getEmail() + " was deleted from the system"); executor.execute("DELETE FROM " + CORE.getTableInfoUsersData() + " WHERE UserId=?", userId); - LoginManager.deleteLoginsRow(deletUser, null); + LoginManager.deleteLoginsRow(deleteUser, null); executor.execute("DELETE FROM " + CORE.getTableInfoPrincipals() + " WHERE UserId=?", userId); ApiKeyManager.get().deleteKeys(new SimpleFilter(FieldKey.fromParts("CreatedBy"), userId)); - OntologyManager.deleteOntologyObject(deletUser.getEntityId(), ContainerManager.getSharedContainer(), true); + OntologyManager.deleteOntologyObject(deleteUser.getEntityId(), ContainerManager.getSharedContainer(), true); + + // GitHub Issue #714: Delete the user's avatar. Avatars use ImageType.Large, but delete all types just in case that changes. + ThumbnailService svc = ThumbnailService.get(); + ThumbnailProvider provider = new AvatarThumbnailProvider(deleteUser); + Arrays.stream(ImageType.values()) + .forEach(imageType -> svc.deleteThumbnail(provider, imageType)); // Clear user list immediately (before the last root admin check) and again after commit/rollback transaction.addCommitTask(UserManager::clearUserList, CommitTaskOption.IMMEDIATE, CommitTaskOption.POSTCOMMIT, CommitTaskOption.POSTROLLBACK); @@ -952,7 +960,7 @@ public static void deleteUser(int userId) throws UserManagementException catch (Exception e) { LOG.error("deleteUser", e); - throw new UserManagementException(deletUser.getEmail(), e); + throw new UserManagementException(deleteUser.getEmail(), e); } //TODO: Delete User files @@ -986,7 +994,7 @@ public static void setUserActive(User currentUser, User userToAdjust, boolean ac if (!errors.isEmpty()) { - Throwable first = errors.get(0); + Throwable first = errors.getFirst(); if (first instanceof RuntimeException) throw (RuntimeException)first; else @@ -1027,7 +1035,7 @@ public static void setUserActive(User currentUser, User userToAdjust, boolean ac } catch(RuntimeSQLException e) { - LOG.error("setUserActive: " + e); + LOG.error("setUserActive", e); throw new UserManagementException(userToAdjust.getEmail(), e); } } From 6b33474b20a7efbb954f34973c8acf006301f50b Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 17 Mar 2026 17:49:32 -0700 Subject: [PATCH 2/2] @NotNull --- api/src/org/labkey/api/thumbnail/ThumbnailService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/org/labkey/api/thumbnail/ThumbnailService.java b/api/src/org/labkey/api/thumbnail/ThumbnailService.java index 6d90c361cfa..b27c4554ede 100644 --- a/api/src/org/labkey/api/thumbnail/ThumbnailService.java +++ b/api/src/org/labkey/api/thumbnail/ThumbnailService.java @@ -15,6 +15,7 @@ */ package org.labkey.api.thumbnail; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.CacheableWriter; import org.labkey.api.data.views.DataViewProvider.EditInfo.ThumbnailType; @@ -23,18 +24,17 @@ import org.labkey.api.view.ViewContext; import java.io.IOException; +import java.util.Objects; import java.util.Set; /** * Works with {@link ThumbnailProvider} implementations to cache thumbnails. - * User: adam - * Date: 10/8/11 */ public interface ThumbnailService { - static ThumbnailService get() + static @NotNull ThumbnailService get() { - return ServiceRegistry.get().getService(ThumbnailService.class); + return Objects.requireNonNull(ServiceRegistry.get().getService(ThumbnailService.class)); } static void setInstance(ThumbnailService impl)