Skip to content
Draft
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
23 changes: 21 additions & 2 deletions api/src/org/labkey/api/pipeline/PipelineJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import datadog.trace.api.CorrelationIdentifier;
import datadog.trace.api.Trace;
Expand Down Expand Up @@ -1897,12 +1898,30 @@ public static PipelineJob deserializeJob(@NotNull String serialized)
}

public static ObjectMapper createObjectMapper()
{
return createObjectMapper(null);
}

/**
* Build the pipeline-job ObjectMapper. Polymorphic default typing (NON_FINAL) is always active so the concrete
* PipelineJob subclass and its field graph round-trip through {@code @class} type ids on the wire.
*
* @param typeValidator when non-null, default typing is activated with this {@link PolymorphicTypeValidator}
* (a deny-by-default allowlist) instead of the deprecated, unrestricted {@code enableDefaultTyping}. It is consulted
* only on deserialization, so only the pipeline-job deserialize path passes one (see {@code PipelineJacksonTyping});
* serialization and all other callers pass null and keep the historical permissive behavior.
*/
public static ObjectMapper createObjectMapper(@Nullable PolymorphicTypeValidator typeValidator)
{
ObjectMapper mapper = JsonUtil.DEFAULT_MAPPER.copy()
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

if (typeValidator != null)
mapper.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL);
else
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

SimpleModule module = new SimpleModule();
module.addSerializer(new SqlTimeSerialization.SqlTimeSerializer());
Expand Down
13 changes: 13 additions & 0 deletions pipeline/src/org/labkey/pipeline/PipelineController.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
Expand Down Expand Up @@ -101,6 +102,7 @@
import org.labkey.api.util.TestContext;
import org.labkey.api.util.URIUtil;
import org.labkey.api.util.URLHelper;
import org.labkey.api.util.logging.LogHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.HBox;
import org.labkey.api.view.HtmlView;
Expand Down Expand Up @@ -142,6 +144,7 @@
public class PipelineController extends SpringActionController
{
private static final DefaultActionResolver _resolver = new DefaultActionResolver(PipelineController.class);
private static final Logger LOG = LogHelper.getLogger(PipelineController.class, "Pipeline controller actions, including job cancellation");

public enum Params { path, rootset, overrideRoot }

Expand Down Expand Up @@ -1087,13 +1090,23 @@ public boolean handlePost(StatusController.RowIdForm form, BindException errors)
catch (PipelineProvider.HandlerException e)
{
_successURL = StatusController.urlDetails(getContainer(), form.getRowId(), e.getMessage());
// Help diagnose rare but recurring test failure in PipelineCancelTest
LOG.info("CancelJobAction.handlePost: cancel rejected for rowId {} ({}); _successURL set to details page {}", form.getRowId(), e.getMessage(), _successURL);
}
return true;
}

@Override
public URLHelper getSuccessURL(StatusController.RowIdForm rowIdForm)
{
if (_successURL == null)
{
// Diagnostic (PipelineCancelTest intermittent blank page after cancel): capture the redirect target and the response state at the moment the framework
// decides between issuing the redirect and rendering an (empty) view. A null _successURL here, or a response
// that is already committed, would explain the observed 200/empty-body response instead of a 302.
HttpServletResponse response = getViewContext().getResponse();
LOG.info("CancelJobAction.getSuccessURL: returning _successURL={}, response.isCommitted={}, response.status={}", _successURL, response.isCommitted(), response.getStatus());
}
return _successURL;
}
}
Expand Down
13 changes: 12 additions & 1 deletion pipeline/src/org/labkey/pipeline/PipelineModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
import org.labkey.api.pipeline.trigger.PipelineTriggerRegistry;
import org.labkey.api.pipeline.trigger.PipelineTriggerType;
import org.labkey.api.security.User;
import org.labkey.api.settings.OptionalFeatureFlag;
import org.labkey.api.settings.OptionalFeatureService;
import org.labkey.api.settings.OptionalFeatureService.FeatureType;
import org.labkey.api.usageMetrics.UsageMetricsService;
import org.labkey.api.util.ContextListener;
import org.labkey.api.util.PageFlowUtil;
Expand All @@ -71,6 +74,7 @@
import org.labkey.pipeline.api.CommandLineTokenizer;
import org.labkey.pipeline.api.ExecTaskFactory;
import org.labkey.pipeline.api.PipelineEmailPreferences;
import org.labkey.pipeline.api.PipelineJacksonTyping;
import org.labkey.pipeline.api.PipelineJobMarshaller;
import org.labkey.pipeline.api.PipelineJobServiceImpl;
import org.labkey.pipeline.api.PipelineManager;
Expand All @@ -89,6 +93,7 @@
import org.labkey.pipeline.mule.EPipelineQueueImpl;
import org.labkey.pipeline.mule.RemoteServerStartup;
import org.labkey.pipeline.mule.filters.TaskJmsSelectorFilter;
import org.labkey.pipeline.mule.transformers.PipelineXStreamSecurity;
import org.labkey.pipeline.status.StatusController;
import org.labkey.pipeline.trigger.PipelineTriggerRegistryImpl;
import org.labkey.pipeline.validators.PipelineSetupValidatorFactory;
Expand Down Expand Up @@ -206,6 +211,10 @@ protected void startupAfterSpringConfig(ModuleContext moduleContext)
StatusController.registerAdminConsoleLinks();
WebdavService.get().addProvider(new PipelineWebdavProvider());

OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(PipelineJacksonTyping.FEATUREFLAG_DISABLE_JOB_TYPE_ALLOWLIST,
"Disable pipeline job deserialization type allowlist",
"Reverts pipeline job JSON deserialization to unrestricted Jackson default typing instead of the deny-by-default type allowlist. Enable only as a temporary escape hatch if the allowlist rejects a legitimate job type.", false, true, FeatureType.Deprecated));

if (null != FileContentService.get())
FileContentService.get().addFileListener(new TableUpdaterFileListener(PipelineSchema.getInstance().getTableInfoStatusFiles(), "FilePath", TableUpdaterFileListener.Type.filePathForwardSlash, "RowId"));
SiteValidationService svc = SiteValidationService.get();
Expand Down Expand Up @@ -323,7 +332,9 @@ public void containerDeleted(Container c, User user)
PathMapperImpl.TestCase.class,
PipelineCommandTestCase.class,
PipelineJobMarshaller.TestCase.class,
PipelineJobServiceImpl.TestCase.class
PipelineJacksonTyping.TestCase.class,
PipelineJobServiceImpl.TestCase.class,
PipelineXStreamSecurity.TestCase.class
);
}

Expand Down
Loading