From 5dd0eed5545c5f4f95d84de79c30dedf46aa8cf0 Mon Sep 17 00:00:00 2001
From: Binal Patel
Date: Wed, 10 Dec 2025 09:40:31 -0800
Subject: [PATCH 1/6] Issue 54076: Antivirus Support: Improve error messaging
for ClamAV-flagged uploads with link to client guidance (#7238)
* Update error message
* Move error msg to a constant in AntiVirusService
---
api/src/org/labkey/api/premium/AntiVirusService.java | 1 +
core/src/org/labkey/core/webdav/DavController.java | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/api/src/org/labkey/api/premium/AntiVirusService.java b/api/src/org/labkey/api/premium/AntiVirusService.java
index 20a31e5d897..5e15f09145e 100644
--- a/api/src/org/labkey/api/premium/AntiVirusService.java
+++ b/api/src/org/labkey/api/premium/AntiVirusService.java
@@ -35,6 +35,7 @@ public interface AntiVirusService
{
// NOTE purposefully this is not the same as the standard test file: ...EICAR-STANDARD-ANTIVIRUS-TEST-FILE...
String TEST_VIRUS_CONTENT="X5O!P%@AP[4\\PZX54(P^)7CC)7}$LABKEY-ANTIVIRUS-TEST-FILE!$H+H*";
+ String MALICIOUS_FILE_ERROR_MESSAGE = "For security reasons, we did not upload this file. Please contact your internal IT or Information Security department for assistance in dealing with this potentially harmful file.";
static @Nullable AntiVirusService get()
{
diff --git a/core/src/org/labkey/core/webdav/DavController.java b/core/src/org/labkey/core/webdav/DavController.java
index 58c7bbb0112..6c680148437 100644
--- a/core/src/org/labkey/core/webdav/DavController.java
+++ b/core/src/org/labkey/core/webdav/DavController.java
@@ -3002,7 +3002,7 @@ public void closeInputStream() throws IOException
if (result.result == AntiVirusService.Result.CONFIGURATION_ERROR)
throw new ConfigurationException(result.message);
else
- throw new DavException(WebdavStatus.SC_BAD_REQUEST, result.message);
+ throw new DavException(WebdavStatus.SC_BAD_REQUEST, result.message + ". " + AntiVirusService.MALICIOUS_FILE_ERROR_MESSAGE);
}
}
From 91d368119fbe2957521761d38a749c8900463f7a Mon Sep 17 00:00:00 2001
From: Adam Rauch
Date: Wed, 10 Dec 2025 09:58:01 -0800
Subject: [PATCH 2/6] Write file paths, etc. (#7229)
---
.../ImportProgress.java} | 49 +++++++++----------
api/src/org/labkey/api/dataiterator/Pump.java | 5 +-
.../labkey/api/exp/list/ListDefinition.java | 5 +-
.../DatabaseMigrationConfiguration.java | 8 ++-
.../DefaultMigrationSchemaHandler.java | 11 +++--
.../labkey/api/migration/FilePathWriter.java | 42 ++++++++++++++++
.../api/migration/MigrationSchemaHandler.java | 3 ++
.../org/labkey/api/module/ModuleLoader.java | 3 --
.../DataClassMigrationSchemaHandler.java | 14 ++++--
.../ExperimentMigrationSchemaHandler.java | 9 ++++
.../SampleTypeMigrationSchemaHandler.java | 8 +++
.../labkey/list/model/ListDefinitionImpl.java | 6 +--
.../list/model/ListQueryUpdateService.java | 4 +-
.../src/org/labkey/search/SearchModule.java | 8 +++
.../specimen/importer/SpecimenImporter.java | 4 +-
15 files changed, 130 insertions(+), 49 deletions(-)
rename api/src/org/labkey/api/{exp/list/ListImportProgress.java => dataiterator/ImportProgress.java} (81%)
create mode 100644 api/src/org/labkey/api/migration/FilePathWriter.java
diff --git a/api/src/org/labkey/api/exp/list/ListImportProgress.java b/api/src/org/labkey/api/dataiterator/ImportProgress.java
similarity index 81%
rename from api/src/org/labkey/api/exp/list/ListImportProgress.java
rename to api/src/org/labkey/api/dataiterator/ImportProgress.java
index 0210a1fd341..f118003f82c 100644
--- a/api/src/org/labkey/api/exp/list/ListImportProgress.java
+++ b/api/src/org/labkey/api/dataiterator/ImportProgress.java
@@ -1,27 +1,22 @@
-/*
- * Copyright (c) 2009-2014 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.api.exp.list;
-
-/*
-* User: adam
-* Date: Dec 23, 2009
-* Time: 3:05:51 PM
-*/
-public interface ListImportProgress
-{
- void setTotalRows(int rows);
- void setCurrentRow(int currentRow);
-}
+/*
+ * Copyright (c) 2009-2014 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.api.dataiterator;
+
+public interface ImportProgress
+{
+ void setTotalRows(int rows);
+ void setCurrentRow(int currentRow);
+}
diff --git a/api/src/org/labkey/api/dataiterator/Pump.java b/api/src/org/labkey/api/dataiterator/Pump.java
index fd15dd76940..83e5b41cfa8 100644
--- a/api/src/org/labkey/api/dataiterator/Pump.java
+++ b/api/src/org/labkey/api/dataiterator/Pump.java
@@ -17,7 +17,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.labkey.api.exp.list.ListImportProgress;
import org.labkey.api.query.BatchValidationException;
import java.io.IOException;
@@ -34,7 +33,7 @@ public class Pump implements Runnable
final BatchValidationException _errors;
int _errorLimit = Integer.MAX_VALUE;
long _rowCount = 0;
- ListImportProgress _progress = null;
+ ImportProgress _progress = null;
public Pump(DataIterator it, DataIteratorContext context)
{
@@ -50,7 +49,7 @@ public Pump(DataIteratorBuilder builder, DataIteratorContext context)
_errors = context.getErrors();
}
- public void setProgress(ListImportProgress progress)
+ public void setProgress(ImportProgress progress)
{
_progress = progress;
}
diff --git a/api/src/org/labkey/api/exp/list/ListDefinition.java b/api/src/org/labkey/api/exp/list/ListDefinition.java
index 69d4473c22b..9963ad6b0bd 100644
--- a/api/src/org/labkey/api/exp/list/ListDefinition.java
+++ b/api/src/org/labkey/api/exp/list/ListDefinition.java
@@ -23,6 +23,7 @@
import org.labkey.api.data.ContainerFilter;
import org.labkey.api.data.LookupResolutionType;
import org.labkey.api.data.TableInfo;
+import org.labkey.api.dataiterator.ImportProgress;
import org.labkey.api.exp.DomainNotFoundException;
import org.labkey.api.exp.PropertyType;
import org.labkey.api.exp.property.Domain;
@@ -269,8 +270,8 @@ public static BodySetting getForValue(int value)
ListItem getListItemForEntityId(String entityId, User user);
int insertListItems(User user, Container container, List listItems) throws IOException;
- int insertListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ListImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType) throws IOException;
- int importListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ListImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType, QueryUpdateService.InsertOption insertOption) throws IOException;
+ int insertListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType) throws IOException;
+ int importListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType, QueryUpdateService.InsertOption insertOption) throws IOException;
@Nullable TableInfo getTable(User user);
@Nullable TableInfo getTable(User user, Container c);
diff --git a/api/src/org/labkey/api/migration/DatabaseMigrationConfiguration.java b/api/src/org/labkey/api/migration/DatabaseMigrationConfiguration.java
index 49cd45b4ff5..60a48a6e791 100644
--- a/api/src/org/labkey/api/migration/DatabaseMigrationConfiguration.java
+++ b/api/src/org/labkey/api/migration/DatabaseMigrationConfiguration.java
@@ -7,7 +7,10 @@
import org.labkey.api.data.DbScope;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.TableSelector;
+import org.labkey.api.util.GUID;
+import org.labkey.api.util.Pair;
+import java.io.PrintWriter;
import java.util.Set;
import java.util.function.Predicate;
@@ -21,5 +24,8 @@ default void beforeMigration(){}
Predicate getColumnNameFilter();
@Nullable TableSelector getTableSelector(DbSchemaType schemaType, TableInfo sourceTable, TableInfo targetTable, Set selectColumnNames, MigrationSchemaHandler schemaHandler, @Nullable MigrationTableHandler tableHandler);
default void copyAttachments(DbSchema sourceSchema, DbSchema targetSchema, MigrationSchemaHandler schemaHandler){}
- default void afterMigration(){}
+ default @Nullable Pair> initializeFilePathWriter()
+ {
+ return null;
+ }
}
diff --git a/api/src/org/labkey/api/migration/DefaultMigrationSchemaHandler.java b/api/src/org/labkey/api/migration/DefaultMigrationSchemaHandler.java
index 36fb4089026..5eb1348abc3 100644
--- a/api/src/org/labkey/api/migration/DefaultMigrationSchemaHandler.java
+++ b/api/src/org/labkey/api/migration/DefaultMigrationSchemaHandler.java
@@ -306,15 +306,15 @@ public static void afterMigration() throws InterruptedException
if (unseen.isEmpty())
LOG.info("All AttachmentTypes have been seen");
else
- throw new ConfigurationException("These AttachmentTypes have not been seen: " + unseen.stream().map(type -> type.getClass().getSimpleName()).collect(Collectors.joining(", ")));
+ LOG.error("These AttachmentTypes have not been seen: {}", unseen.stream().map(type -> type.getClass().getSimpleName()).collect(Collectors.joining(", ")));
// Shut down the attachment JobRunner
LOG.info("Waiting for attachments background transfer to complete");
ATTACHMENT_JOB_RUNNER.shutdown();
- if (ATTACHMENT_JOB_RUNNER.awaitTermination(1, TimeUnit.HOURS))
+ if (ATTACHMENT_JOB_RUNNER.awaitTermination(2, TimeUnit.HOURS))
LOG.info("Attachments background transfer is complete");
else
- LOG.error("Attachments background transfer did not complete after one hour! Giving up.");
+ LOG.error("Attachments background transfer did not complete after two hours! Giving up.");
}
@Override
@@ -332,4 +332,9 @@ public void afterSchema(DatabaseMigrationConfiguration configuration, DbSchema s
public void afterMigration(DatabaseMigrationConfiguration configuration)
{
}
+
+ @Override
+ public void writeFilePaths(FilePathWriter writer, Set guids)
+ {
+ }
}
diff --git a/api/src/org/labkey/api/migration/FilePathWriter.java b/api/src/org/labkey/api/migration/FilePathWriter.java
new file mode 100644
index 00000000000..b034541c009
--- /dev/null
+++ b/api/src/org/labkey/api/migration/FilePathWriter.java
@@ -0,0 +1,42 @@
+package org.labkey.api.migration;
+
+import org.labkey.api.data.ContainerManager;
+import org.labkey.api.files.FileContentService;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+
+public class FilePathWriter implements Closeable
+{
+ private final PrintWriter _out;
+ private final Path _rootPath;
+
+ public FilePathWriter(PrintWriter out)
+ {
+ _out = out;
+ _rootPath = FileContentService.get().getFileRoot(ContainerManager.getRoot()).toPath();
+ }
+
+ public void write(File file)
+ {
+ _out.println(_rootPath.relativize(file.toPath()).normalize());
+ }
+
+ public void println(String s)
+ {
+ _out.println(s);
+ }
+
+ public void println()
+ {
+ _out.println();
+ }
+
+ @Override
+ public void close()
+ {
+ _out.close();
+ }
+}
diff --git a/api/src/org/labkey/api/migration/MigrationSchemaHandler.java b/api/src/org/labkey/api/migration/MigrationSchemaHandler.java
index daa6aaa02e3..ebb41405841 100644
--- a/api/src/org/labkey/api/migration/MigrationSchemaHandler.java
+++ b/api/src/org/labkey/api/migration/MigrationSchemaHandler.java
@@ -12,6 +12,7 @@
import org.labkey.api.query.FieldKey;
import org.labkey.api.util.GUID;
+import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -57,4 +58,6 @@ public interface MigrationSchemaHandler
@NotNull Collection getAttachmentTypes();
void afterMigration(DatabaseMigrationConfiguration configuration);
+
+ void writeFilePaths(FilePathWriter writer, Set guids);
}
diff --git a/api/src/org/labkey/api/module/ModuleLoader.java b/api/src/org/labkey/api/module/ModuleLoader.java
index 49d5743d602..b8839fd0947 100644
--- a/api/src/org/labkey/api/module/ModuleLoader.java
+++ b/api/src/org/labkey/api/module/ModuleLoader.java
@@ -678,9 +678,6 @@ private void doInit(Execution execution) throws ServletException
_log.info("Server installation GUID: {}, server session GUID: {}", AppProps.getInstance().getServerGUID(), AppProps.getInstance().getServerSessionGUID());
_log.info("Deploying to context path {}", AppProps.getInstance().getContextPath());
- // Temporary logging to help track down issues we're having with upgrading XMLBeans from v5.2.0. TODO: Remove after upgrade
- _log.info("XMLBeans version: {}", XmlBeans.getVersion());
-
synchronized (_modulesLock)
{
checkForRenamedModules();
diff --git a/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java
index 2e93cba7b5d..296b2b349b5 100644
--- a/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java
+++ b/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java
@@ -24,6 +24,7 @@
import org.labkey.api.migration.DatabaseMigrationService.DataFilter;
import org.labkey.api.migration.DefaultMigrationSchemaHandler;
import org.labkey.api.migration.ExperimentDeleteService;
+import org.labkey.api.migration.FilePathWriter;
import org.labkey.api.query.FieldKey;
import org.labkey.api.util.GUID;
import org.labkey.api.util.StringUtilsLabKey;
@@ -95,8 +96,8 @@ public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilte
// Select all ObjectIds associated with the not-copied rows from the source database
SQLFragment objectIdSql = new SQLFragment("SELECT ObjectId FROM exp.Data d INNER JOIN ")
- .appendIdentifier(sourceTable.getSelectName())
- .append(" dc ON d.LSID = dc.LSID");
+ .appendIdentifier(sourceTable.getSelectName())
+ .append(" dc ON d.LSID = dc.LSID");
// Don't create an empty IN clause; need to work around issue where "NOT xxx IN (NULL)" evaluates to NULL.
if (!copiedLsids.isEmpty())
@@ -134,7 +135,7 @@ public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilte
}
})
.forEach(SEQUENCE_IDS::add);
- LOG.info("{} added to the SequenceIdentity set",
+ LOG.info(" {} added to the SequenceIdentity set",
StringUtilsLabKey.pluralize(SEQUENCE_IDS.size() - startSize, "unique SequenceId was", "unique SequenceIds were"));
}
}
@@ -202,4 +203,11 @@ public FilterClause getTableFilterClause(TableInfo sourceTable, Set contai
{
return List.of(ExpDataClassType.get());
}
+
+ @Override
+ public void writeFilePaths(FilePathWriter writer, Set guids)
+ {
+ // TODO: Enumerate FileLink fields in data classes in the filtered containers (guids) and write out those file
+ // paths. Current client has none.
+ }
}
diff --git a/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java
index 3a1bfb1d6bb..6d844f2864b 100644
--- a/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java
+++ b/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java
@@ -261,6 +261,15 @@ public FilterClause getContainerClause(TableInfo sourceTable, Set containe
.appendInClause(containers, sourceTable.getSqlDialect())
.append(")")
);
+ case "MaterialAliasMap" -> new AndClause(
+ new InClause(FieldKey.fromParts("Container"), containers),
+ // The below effectively matches the "Material" conditions above, since MaterialAliasMap has an FK to Material
+ new InClause(FieldKey.fromParts("LSID", "Container"), containers),
+ new OrClause(
+ new CompareClause(FieldKey.fromParts("LSID", "RunId"), CompareType.ISBLANK, null),
+ new InClause(FieldKey.fromParts("LSID", "RunId", "Container"), containers)
+ )
+ );
case "ObjectLegacyNames" -> new SQLClause(
new SQLFragment("ObjectId IN (SELECT ObjectId FROM exp.Object WHERE Container")
.appendInClause(containers, sourceTable.getSqlDialect())
diff --git a/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java
index 4c2d2f89d42..0aae0fa7aa6 100644
--- a/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java
+++ b/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java
@@ -14,6 +14,7 @@
import org.labkey.api.exp.api.SampleTypeDomainKind;
import org.labkey.api.migration.DatabaseMigrationService.DataFilter;
import org.labkey.api.migration.DefaultMigrationSchemaHandler;
+import org.labkey.api.migration.FilePathWriter;
import org.labkey.api.util.GUID;
import org.labkey.api.util.logging.LogHelper;
@@ -168,4 +169,11 @@ public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilte
ExperimentMigrationSchemaHandler.deleteObjectIds(objectIdClause);
}
}
+
+ @Override
+ public void writeFilePaths(FilePathWriter writer, Set guids)
+ {
+ // TODO: Enumerate FileLink fields in sample types in the filtered containers (guids) and write out those file
+ // paths. Current client has none.
+ }
}
diff --git a/list/src/org/labkey/list/model/ListDefinitionImpl.java b/list/src/org/labkey/list/model/ListDefinitionImpl.java
index 764607d36b3..b63b108c2e9 100644
--- a/list/src/org/labkey/list/model/ListDefinitionImpl.java
+++ b/list/src/org/labkey/list/model/ListDefinitionImpl.java
@@ -30,13 +30,13 @@
import org.labkey.api.data.SimpleFilter;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.TableSelector;
+import org.labkey.api.dataiterator.ImportProgress;
import org.labkey.api.exp.DomainNotFoundException;
import org.labkey.api.exp.Lsid;
import org.labkey.api.exp.ObjectProperty;
import org.labkey.api.exp.TemplateInfo;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.exp.list.ListDefinition;
-import org.labkey.api.exp.list.ListImportProgress;
import org.labkey.api.exp.list.ListItem;
import org.labkey.api.exp.property.Domain;
import org.labkey.api.exp.property.DomainProperty;
@@ -610,13 +610,13 @@ public int insertListItems(User user, Container container, List listIt
@Override
- public int insertListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ListImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType)
+ public int insertListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType)
{
return importListItems(user, container, loader, errors, attachmentDir, progress, supportAutoIncrementKey, lookupResolutionType, QueryUpdateService.InsertOption.INSERT);
}
@Override
- public int importListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ListImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType, QueryUpdateService.InsertOption insertOption)
+ public int importListItems(User user, Container container, DataLoader loader, @NotNull BatchValidationException errors, @Nullable VirtualFile attachmentDir, @Nullable ImportProgress progress, boolean supportAutoIncrementKey, LookupResolutionType lookupResolutionType, QueryUpdateService.InsertOption insertOption)
{
ListQuerySchema schema = new ListQuerySchema(user, container);
TableInfo table = schema.getTable(_def.getName());
diff --git a/list/src/org/labkey/list/model/ListQueryUpdateService.java b/list/src/org/labkey/list/model/ListQueryUpdateService.java
index 9e6fae5c12f..8848d3d8b76 100644
--- a/list/src/org/labkey/list/model/ListQueryUpdateService.java
+++ b/list/src/org/labkey/list/model/ListQueryUpdateService.java
@@ -42,11 +42,11 @@
import org.labkey.api.dataiterator.DataIteratorBuilder;
import org.labkey.api.dataiterator.DataIteratorContext;
import org.labkey.api.dataiterator.DetailedAuditLogDataIterator;
+import org.labkey.api.dataiterator.ImportProgress;
import org.labkey.api.exp.ObjectProperty;
import org.labkey.api.exp.PropertyType;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.exp.list.ListDefinition;
-import org.labkey.api.exp.list.ListImportProgress;
import org.labkey.api.exp.list.ListItem;
import org.labkey.api.exp.list.ListService;
import org.labkey.api.exp.property.Domain;
@@ -229,7 +229,7 @@ private User getListUser(User user, Container container)
}
public int insertUsingDataIterator(DataLoader loader, User user, Container container, BatchValidationException errors, @Nullable VirtualFile attachmentDir,
- @Nullable ListImportProgress progress, boolean supportAutoIncrementKey, InsertOption insertOption, LookupResolutionType lookupResolutionType)
+ @Nullable ImportProgress progress, boolean supportAutoIncrementKey, InsertOption insertOption, LookupResolutionType lookupResolutionType)
{
if (!_list.isVisible(user))
throw new UnauthorizedException("You do not have permission to insert data into this table.");
diff --git a/search/src/org/labkey/search/SearchModule.java b/search/src/org/labkey/search/SearchModule.java
index d4cd37bca6e..f12c4ea4294 100644
--- a/search/src/org/labkey/search/SearchModule.java
+++ b/search/src/org/labkey/search/SearchModule.java
@@ -29,6 +29,7 @@
import org.labkey.api.data.UpgradeCode;
import org.labkey.api.mbean.LabKeyManagement;
import org.labkey.api.mbean.SearchMXBean;
+import org.labkey.api.migration.DatabaseMigrationConfiguration;
import org.labkey.api.migration.DatabaseMigrationService;
import org.labkey.api.migration.DefaultMigrationSchemaHandler;
import org.labkey.api.module.DefaultModule;
@@ -195,6 +196,13 @@ public List getTablesToCopy()
{
return List.of(); // Leave empty -- target server will re-index all documents
}
+
+ @Override
+ public void afterMigration(DatabaseMigrationConfiguration configuration)
+ {
+ // Clear index and all last indexed tracking
+ SearchService.get().deleteIndex("Database was just migrated");
+ }
});
}
diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java b/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java
index 13e5ed7ba1b..86e5d0465a8 100644
--- a/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java
+++ b/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java
@@ -57,6 +57,7 @@
import org.labkey.api.dataiterator.DataIteratorBuilder;
import org.labkey.api.dataiterator.DataIteratorContext;
import org.labkey.api.dataiterator.DataIteratorUtil;
+import org.labkey.api.dataiterator.ImportProgress;
import org.labkey.api.dataiterator.ListofMapsDataIterator;
import org.labkey.api.dataiterator.LoggingDataIterator;
import org.labkey.api.dataiterator.MapDataIterator;
@@ -69,7 +70,6 @@
import org.labkey.api.exp.api.ExpSampleType;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.exp.api.SampleTypeService;
-import org.labkey.api.exp.list.ListImportProgress;
import org.labkey.api.iterator.MarkableIterator;
import org.labkey.api.pipeline.PipelineJob;
import org.labkey.api.query.DefaultSchema;
@@ -1993,7 +1993,7 @@ public Object getValue(Map row)
DataIteratorBuilder standardEtl = StandardDataIteratorBuilder.forInsert(target, specimenWrapped, getContainer(), getUser(), dix);
DataIteratorBuilder persist = ((UpdateableTableInfo)target).persistRows(standardEtl, dix);
Pump pump = new Pump(persist, dix);
- pump.setProgress(new ListImportProgress()
+ pump.setProgress(new ImportProgress()
{
long heartBeat = HeartBeat.currentTimeMillis();
From f814e445d418566f7566550ade5c391532ec3c2f Mon Sep 17 00:00:00 2001
From: Cory Nathe
Date: Wed, 10 Dec 2025 12:11:45 -0600
Subject: [PATCH 3/6] GitHub Issue 726: Fix typo in URLDisplayColumn img style
width and height semicolon location (#7255)
- make sure semicolon is added after img style width and height
---
api/src/org/labkey/api/data/URLDisplayColumn.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api/src/org/labkey/api/data/URLDisplayColumn.java b/api/src/org/labkey/api/data/URLDisplayColumn.java
index 55bb96d703d..4b5e202fc2c 100644
--- a/api/src/org/labkey/api/data/URLDisplayColumn.java
+++ b/api/src/org/labkey/api/data/URLDisplayColumn.java
@@ -182,7 +182,7 @@ public Renderable createThumbnailImage()
String thumbnailUrl = _fileIconUrl != null ? ensureAbsoluteUrl(_ctx, _fileIconUrl) : ensureAbsoluteUrl(_ctx, _url);
return IMG(at(
- style, "display:block; vertical-align:middle;" + (renderSize ? (_thumbnailWidth != null ? " width:" + _thumbnailWidth : " max-width:32px" + "; ") + (_thumbnailHeight != null ? " height:" + _thumbnailHeight : " height:auto" + ";") : ""),
+ style, "display:block; vertical-align:middle;" + (renderSize ? (_thumbnailWidth != null ? " width:" + _thumbnailWidth : " max-width:32px") + "; " + (_thumbnailHeight != null ? " height:" + _thumbnailHeight : " height:auto") + ";" : ""),
src, thumbnailUrl, title, _displayName
));
}
From 0be86528c31977cb4194f57057ea7a4c07381c2e Mon Sep 17 00:00:00 2001
From: bbimber
Date: Sat, 13 Dec 2025 14:25:35 -0800
Subject: [PATCH 4/6] Do not relativize ExpData URIs when making the LSID
unless under file root (#7261)
---
.../experiment/api/ExperimentServiceImpl.java | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
index f3d51bf42b7..705e05c82d6 100644
--- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
+++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java
@@ -230,13 +230,13 @@
import org.labkey.api.util.StringUtilsLabKey;
import org.labkey.api.util.SubstitutionFormat;
import org.labkey.api.util.TestContext;
+import org.labkey.api.util.URIUtil;
import org.labkey.api.util.UnexpectedException;
import org.labkey.api.util.logging.LogHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.HttpView;
import org.labkey.api.view.JspTemplate;
import org.labkey.api.view.JspView;
-import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.UnauthorizedException;
import org.labkey.api.view.ViewBackgroundInfo;
import org.labkey.api.view.ViewContext;
@@ -244,7 +244,6 @@
import org.labkey.experiment.FileLinkFileListener;
import org.labkey.experiment.MissingFilesCheckInfo;
import org.labkey.experiment.XarExportType;
-import org.labkey.experiment.XarExporter;
import org.labkey.experiment.XarReader;
import org.labkey.experiment.api.property.DomainPropertyManager;
import org.labkey.experiment.controllers.exp.ExperimentController;
@@ -253,13 +252,11 @@
import org.labkey.experiment.pipeline.ExperimentPipelineJob;
import org.labkey.experiment.pipeline.MoveRunsPipelineJob;
import org.labkey.experiment.xar.AutoFileLSIDReplacer;
-import org.labkey.experiment.xar.XarExportSelection;
import org.labkey.vfs.FileLike;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.PessimisticLockingFailureException;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
@@ -778,7 +775,17 @@ public ExpDataImpl createData(URI uri, XarSource source) throws XarFormatExcepti
{
path = FileUtil.stringToPath(source.getXarContext().getContainer(),
source.getCanonicalDataFileURL(FileUtil.pathToString(path)));
- pathStr = FileUtil.relativizeUnix(source.getRootPath(), path, false);
+
+ // Only convert to a relative path if this is a descendant of the root:
+ path = path.normalize();
+ if (URIUtil.isDescendant(source.getRootPath().toUri(), path.toUri()))
+ {
+ pathStr = FileUtil.relativizeUnix(source.getRootPath(), path, false);
+ }
+ else
+ {
+ pathStr = FileUtil.pathToString(path);
+ }
}
catch (IOException e)
{
From 186295bd8113ccbd2e835fd49be81c25e2358176 Mon Sep 17 00:00:00 2001
From: Cory Nathe
Date: Mon, 15 Dec 2025 08:11:25 -0600
Subject: [PATCH 5/6] Allow site admins to see all views (private and shared)
from the hidden query-manageViews page (#7262)
- allow site admin to see all container custom views from query-manageViews action
- set table css classes to make the static table more readable
---
.../query/controllers/InternalViewForm.java | 3 +-
.../src/org/labkey/query/view/manageViews.jsp | 37 +++++++++++--------
2 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/query/src/org/labkey/query/controllers/InternalViewForm.java b/query/src/org/labkey/query/controllers/InternalViewForm.java
index c8bc14c560a..0a8612e3b96 100644
--- a/query/src/org/labkey/query/controllers/InternalViewForm.java
+++ b/query/src/org/labkey/query/controllers/InternalViewForm.java
@@ -69,7 +69,8 @@ public static void checkEdit(ViewContext context, CstmView view)
}
else
{
- if (view.getCustomViewOwner().intValue() != context.getUser().getUserId())
+ // must be owner or site admin
+ if (!context.getUser().hasSiteAdminPermission() && view.getCustomViewOwner().intValue() != context.getUser().getUserId())
{
throw new UnauthorizedException();
}
diff --git a/query/src/org/labkey/query/view/manageViews.jsp b/query/src/org/labkey/query/view/manageViews.jsp
index e5196c99437..d4f52746f04 100644
--- a/query/src/org/labkey/query/view/manageViews.jsp
+++ b/query/src/org/labkey/query/view/manageViews.jsp
@@ -59,14 +59,16 @@
QueryManager mgr = QueryManager.get();
List views = new ArrayList<>();
- if (getViewContext().hasPermission(UpdatePermission.class))
+ if (getViewContext().getUser().hasSiteAdminPermission())
{
- views.addAll(mgr.getCstmViews(c, schemaName, queryName, null, null, false, true));
+ views.addAll(mgr.getCstmViews(c, schemaName, queryName, null, null, false, false));
}
-
- if (!user.isGuest())
+ else
{
- views.addAll(mgr.getCstmViews(c, schemaName, queryName, null, user, false, false));
+ if (getViewContext().hasPermission(UpdatePermission.class))
+ views.addAll(mgr.getCstmViews(c, schemaName, queryName, null, null, false, true));
+ if (!user.isGuest())
+ views.addAll(mgr.getCstmViews(c, schemaName, queryName, null, user, false, false));
}
// UNDONE: Requires queryName and schemaName for now. We need a method to get all session views in a container.
@@ -107,22 +109,25 @@
<% } %>
-
+
- | Schema |
- Query |
- View Name |
- Flags |
- Owner |
- Created |
- Created By |
- Modified |
- Modified By |
+ Schema |
+ Query |
+ View Name |
+ Flags |
+ Owner |
+ Created |
+ Created By |
+ Modified |
+ Modified By |
+ |
<% if (getViewContext().hasPermission(UpdatePermission.class))
{
+ int count = 1;
for (CstmView view : views)
{
+ count++;
List flags = new ArrayList<>();
if (view.getCustomViewId() == 0)
flags.add("session");
@@ -133,7 +138,7 @@
if (mgr.isSnapshot(view.getFlags()))
flags.add("shapshot");
%>
-
+
| <%=h(view.getSchema())%>
|
<%=h(view.getQueryName())%>
From e1da7bd1f13c265e41e31999ec9e88925d83d04b Mon Sep 17 00:00:00 2001
From: Nick Kerr
Date: Mon, 15 Dec 2025 17:47:12 -0800
Subject: [PATCH 6/6] Additional action logging (#7270)
---
.../api/action/PermissionCheckableAction.java | 17 ++++++++++++++++-
api/src/org/labkey/api/data/Container.java | 13 +++++++++++--
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/api/src/org/labkey/api/action/PermissionCheckableAction.java b/api/src/org/labkey/api/action/PermissionCheckableAction.java
index 5795a2e2693..984aa69a8a4 100644
--- a/api/src/org/labkey/api/action/PermissionCheckableAction.java
+++ b/api/src/org/labkey/api/action/PermissionCheckableAction.java
@@ -16,6 +16,7 @@
package org.labkey.api.action;
import jakarta.servlet.http.HttpServletResponse;
+import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.data.Container;
import org.labkey.api.module.IgnoresForbiddenProjectCheck;
@@ -39,6 +40,7 @@
import org.labkey.api.security.roles.RoleManager;
import org.labkey.api.util.ConfigurationException;
import org.labkey.api.util.HttpUtil;
+import org.labkey.api.util.logging.LogHelper;
import org.labkey.api.view.BadRequestException;
import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.RedirectException;
@@ -55,6 +57,7 @@
public abstract class PermissionCheckableAction implements Controller, PermissionCheckable, HasViewContext
{
+ private static final Logger LOG = LogHelper.getLogger(PermissionCheckableAction.class, "Permission checks for actions");
private static final HttpUtil.Method[] arrayGetPost = new HttpUtil.Method[] {Method.GET, Method.POST};
private ViewContext _context = null;
UnauthorizedException.Type _unauthorizedType = UnauthorizedException.Type.redirectToLogin;
@@ -148,6 +151,8 @@ private void _checkActionPermissions(Set contextualRoles) throws Unauthori
Container c = context.getContainer();
User user = context.getUser();
Class extends Controller> actionClass = getClass();
+ if (LOG.isDebugEnabled())
+ LOG.debug(actionClass.getName() + ": checking permissions for user " + (user == null ? "" : user.getName() + " (impersonated=" + user.isImpersonated() + ")"));
if (!actionClass.isAnnotationPresent(IgnoresForbiddenProjectCheck.class))
c.throwIfForbiddenProject(user);
@@ -159,18 +164,22 @@ private void _checkActionPermissions(Set contextualRoles) throws Unauthori
methodsAllowed = methodsAllowedAnnotation.value();
if (Arrays.stream(methodsAllowed).noneMatch(s -> s.equals(method)))
{
- throw new BadRequestException("Method Not Allowed: " + method, null, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ String msg = "Method Not Allowed: " + method;
+ LOG.debug(msg);
+ throw new BadRequestException(msg, null, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
boolean requiresSiteAdmin = actionClass.isAnnotationPresent(RequiresSiteAdmin.class);
if (requiresSiteAdmin && !user.hasSiteAdminPermission())
{
+ LOG.debug(actionClass.getName() + ": action requires site admin permissions");
throw new UnauthorizedException();
}
boolean requiresLogin = actionClass.isAnnotationPresent(RequiresLogin.class);
if (requiresLogin && user.isGuest())
{
+ LOG.debug(actionClass.getName() + ": action requires login (non-guest)");
throw new UnauthorizedException();
}
@@ -214,7 +223,10 @@ private void _checkActionPermissions(Set contextualRoles) throws Unauthori
// Must have all permissions in permissionsRequired
if (!SecurityManager.hasAllPermissions(this.getClass().getName()+"_checkActionPermissions",
c, user, permissionsRequired, contextualRoles))
+ {
+ LOG.debug(actionClass.getName() + ": action requires all permissions: " + permissionsRequired);
throw new UnauthorizedException();
+ }
CSRF.Method csrfCheck = actionClass.isAnnotationPresent(CSRF.class) ? actionClass.getAnnotation(CSRF.class).value() : CSRF.Method.POST;
csrfCheck.validate(context);
@@ -228,7 +240,10 @@ private void _checkActionPermissions(Set contextualRoles) throws Unauthori
Collections.addAll(permissionsAnyOf, requiresAnyOf.value());
if (!SecurityManager.hasAnyPermissions(this.getClass().getName() + "_checkActionPermissions",
c, user, permissionsAnyOf, contextualRoles))
+ {
+ LOG.debug(actionClass.getName() + ": action requires any permissions: " + permissionsAnyOf);
throw new UnauthorizedException();
+ }
}
boolean requiresNoPermission = actionClass.isAnnotationPresent(RequiresNoPermission.class);
diff --git a/api/src/org/labkey/api/data/Container.java b/api/src/org/labkey/api/data/Container.java
index c55c45cd561..d8922b085fc 100644
--- a/api/src/org/labkey/api/data/Container.java
+++ b/api/src/org/labkey/api/data/Container.java
@@ -41,6 +41,7 @@
import org.labkey.api.query.QueryService;
import org.labkey.api.security.HasPermission;
import org.labkey.api.security.SecurableResource;
+import org.labkey.api.security.SecurityLogger;
import org.labkey.api.security.SecurityManager;
import org.labkey.api.security.SecurityPolicy;
import org.labkey.api.security.SecurityPolicyManager;
@@ -549,7 +550,11 @@ private boolean handleForbiddenProject(User user, Set contextualRoles, boo
if (null != impersonationProject && !impersonationProject.equals(currentProject))
{
if (shouldThrow)
- throw new ForbiddenProjectException("You are not allowed to access this folder while impersonating within a different project.");
+ {
+ String msg = "You are not allowed to access this folder while impersonating within a different project.";
+ SecurityLogger.log(msg, user, null, null);
+ throw new ForbiddenProjectException(msg);
+ }
return true;
}
@@ -562,7 +567,11 @@ private boolean handleForbiddenProject(User user, Set contextualRoles, boo
if (lockState.isLocked() && ContainerManager.LOCKED_PROJECT_HANDLER.isForbidden(currentProject, user, contextualRoles, lockState))
{
if (shouldThrow)
- throw new ForbiddenProjectException("You are not allowed to access this folder; it is " + lockState.getDescription() + ".");
+ {
+ String msg = "You are not allowed to access this folder; it is " + lockState.getDescription() + ".";
+ SecurityLogger.log(msg, user, null, null);
+ throw new ForbiddenProjectException(msg);
+ }
return true;
}
|