From f72ed4edab73ce0f5f6fdde5696f351c99e040aa Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 20 May 2026 22:50:13 +0200 Subject: [PATCH] Harden timestamp filter validation null handling --- .../Endpoints/Alterations/Submit/Endpoint.cs | 2 +- .../Filters/WorkflowInstanceFilter.cs | 6 ++++ .../AlterationsApiTimestampFilterTests.cs | 32 +++++++++++++++++++ .../WorkflowInstanceFilterTimestampTests.cs | 9 ++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs index af934067f..8b2319f1e 100644 --- a/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs +++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs @@ -53,7 +53,7 @@ public class Submit : ElsaEndpoint private bool ValidateInput(AlterationPlanParams planParams) { - var errors = WorkflowInstanceFilter.ValidateTimestampFilters(planParams.Filter.TimestampFilters).ToList(); + var errors = WorkflowInstanceFilter.ValidateTimestampFilters(planParams.Filter?.TimestampFilters).ToList(); foreach (var error in errors) AddError(error); diff --git a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs index 2841e98a7..0157107ca 100644 --- a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs +++ b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs @@ -217,6 +217,12 @@ public class WorkflowInstanceFilter foreach (var timestampFilter in timestampFilters) { + if (timestampFilter == null) + { + yield return "Timestamp filter must be specified."; + continue; + } + if (!TryNormalizeTimestampFilterColumn(timestampFilter.Column, out _, out var error)) yield return error; } diff --git a/test/integration/Elsa.Alterations.IntegrationTests/AlterationsApiTimestampFilterTests.cs b/test/integration/Elsa.Alterations.IntegrationTests/AlterationsApiTimestampFilterTests.cs index 196dc7939..2b1b93a39 100644 --- a/test/integration/Elsa.Alterations.IntegrationTests/AlterationsApiTimestampFilterTests.cs +++ b/test/integration/Elsa.Alterations.IntegrationTests/AlterationsApiTimestampFilterTests.cs @@ -78,6 +78,26 @@ public class AlterationsApiTimestampFilterTests : IAsyncLifetime Assert.Contains("Invalid timestamp filter column", body); } + [Fact] + public async Task Submit_WithNullFilter_DoesNotReturnServerError() + { + var response = await _httpClient.PostAsJsonAsync("/alterations/submit", new { filter = (object?)null }); + + Assert.NotEqual(HttpStatusCode.InternalServerError, response.StatusCode); + } + + [Theory] + [InlineData("/alterations/dry-run")] + [InlineData("/alterations/submit")] + public async Task Post_WithNullTimestampFilter_ReturnsBadRequest(string path) + { + var response = await _httpClient.PostAsJsonAsync(path, CreateRequestWithNullTimestampFilter(path)); + var body = await response.Content.ReadAsStringAsync(); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Contains("Timestamp filter must be specified.", body); + } + private static object CreateRequest(string path) { var filter = new @@ -97,4 +117,16 @@ public class AlterationsApiTimestampFilterTests : IAsyncLifetime ? new { filter } : filter; } + + private static object CreateRequestWithNullTimestampFilter(string path) + { + var filter = new + { + timestampFilters = new object?[] { null } + }; + + return path == "/alterations/submit" + ? new { filter } + : filter; + } } diff --git a/test/unit/Elsa.Workflows.Management.UnitTests/Filters/WorkflowInstanceFilterTimestampTests.cs b/test/unit/Elsa.Workflows.Management.UnitTests/Filters/WorkflowInstanceFilterTimestampTests.cs index 9854212e3..4d4468474 100644 --- a/test/unit/Elsa.Workflows.Management.UnitTests/Filters/WorkflowInstanceFilterTimestampTests.cs +++ b/test/unit/Elsa.Workflows.Management.UnitTests/Filters/WorkflowInstanceFilterTimestampTests.cs @@ -87,6 +87,15 @@ public class WorkflowInstanceFilterTimestampTests Assert.Equal("Timestamp filter column must be specified.", error); } + [Fact] + public void ValidateTimestampFilters_WithNullFilter_ReturnsClearValidationError() + { + var errors = WorkflowInstanceFilter.ValidateTimestampFilters([null!]).ToList(); + + var error = Assert.Single(errors); + Assert.Equal("Timestamp filter must be specified.", error); + } + private DateTimeOffset GetMatchingTimestamp(string column) => column switch { nameof(WorkflowInstance.CreatedAt) => _matchingCreatedAt,