From d3569716f3201bdbcd86ba03d508503ae1dc703d Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Fri, 19 Jun 2026 17:34:59 +0200 Subject: [PATCH 01/14] /api/owners/get --- .../owner/getOwnersFiltered.graphql | 5 + .../FWO.Api.Client/Queries/OwnerQueries.cs | 2 + .../Controllers/OwnersController.cs | 158 ++++++++++++++++ .../Requests/GetOwnersRequest.cs | 39 ++++ .../Responses/GetOwnerResponse.cs | 33 ++++ .../files/FWO.Test/OwnersControllerTest.cs | 179 ++++++++++++++++++ 6 files changed, 416 insertions(+) create mode 100644 roles/common/files/fwo-api-calls/owner/getOwnersFiltered.graphql create mode 100644 roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs create mode 100644 roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs create mode 100644 roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs create mode 100644 roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs diff --git a/roles/common/files/fwo-api-calls/owner/getOwnersFiltered.graphql b/roles/common/files/fwo-api-calls/owner/getOwnersFiltered.graphql new file mode 100644 index 0000000000..e0e65efc0a --- /dev/null +++ b/roles/common/files/fwo-api-calls/owner/getOwnersFiltered.graphql @@ -0,0 +1,5 @@ +query getOwnersFiltered($where: owner_bool_exp = {}) { + owner_list: owner(where: $where, order_by: { name: asc }) { + ...ownerDetails + } +} diff --git a/roles/lib/files/FWO.Api.Client/Queries/OwnerQueries.cs b/roles/lib/files/FWO.Api.Client/Queries/OwnerQueries.cs index a8ce1b7d0c..3892dececc 100644 --- a/roles/lib/files/FWO.Api.Client/Queries/OwnerQueries.cs +++ b/roles/lib/files/FWO.Api.Client/Queries/OwnerQueries.cs @@ -8,6 +8,7 @@ public class OwnerQueries : Queries public static readonly string getOwnerById; public static readonly string getOwners; + public static readonly string getOwnersFiltered; public static readonly string getOwnersWithNetworks; public static readonly string getOwnersForRuleOwnerCustomField; public static readonly string getOwnersForRuleOwnerIpBased; @@ -66,6 +67,7 @@ static OwnerQueries() getOwnerById = GetQueryText("owner/getOwnerById.graphql"); getOwners = ownerDetailsFragment + GetQueryText("owner/getOwners.graphql"); + getOwnersFiltered = ownerDetailsFragment + GetQueryText("owner/getOwnersFiltered.graphql"); getOwnersWithNetworks = ownerDetailsFragment + GetQueryText("owner/getOwnersWithNetworks.graphql"); getOwnersForRuleOwnerCustomField = GetQueryText("owner/getOwnersForRuleOwnerCustomField.graphql"); getOwnersForRuleOwnerIpBased = GetQueryText("owner/getOwnersForRuleOwnerIpBased.graphql"); diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs new file mode 100644 index 0000000000..4a73bdbae1 --- /dev/null +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -0,0 +1,158 @@ +using System.Security.Claims; +using FWO.Api.Client; +using FWO.Api.Client.Queries; +using FWO.Basics; +using FWO.Data; +using FWO.Logging; +using FWO.Middleware.Server.Requests; +using FWO.Middleware.Server.Responses; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace FWO.Middleware.Server.Controllers; + +/// +/// Provides owner lookup endpoints. +/// +[Authorize] +[ApiController] +[Route("api/owners")] +public class OwnersController(ApiConnection apiConnection) : ControllerBase +{ + private const string StandardOwnerType = "standard"; + private const string InfrastructureOwnerType = "infrastructure"; + + /// + /// Returns all owners visible to the caller with optional AND-combined filters. + /// + /// + /// Example request bodies: + /// + /// {} + /// + /// + /// {"active":true,"ownerLifecycleStateId":1} + /// + /// + /// {"ownerId":42} + /// + /// + /// {"name":"Finance*","appIdExternal":"APP-?"} + /// + /// Example response: + /// + /// [ + /// {"id":42,"name":"Finance Portal","appIdExternal":"APP-4711","type":"standard"}, + /// {"id":43,"name":"Finance Network","appIdExternal":"NET-4712","type":"infrastructure"} + /// ] + /// + /// The name and appIdExternal filters are case-insensitive and accept * for any + /// character sequence and ? for a single character. Plain text without wildcards is matched as a contains search. + /// + [HttpPost("get")] + [Consumes("application/json")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + [Authorize(Roles = $"{Roles.Auditor}, {Roles.Admin}, {Roles.Modeller}")] + public async Task>> Get([FromBody] GetOwnersRequest? request) + { + try + { + List owners = await apiConnection.SendQueryAsync>( + OwnerQueries.getOwnersFiltered, + BuildQueryVariables(request ?? new GetOwnersRequest(), User)) ?? []; + + return Ok(owners.Select(ToResponse).ToList()); + } + catch (Exception exception) + { + Log.WriteError("Get Owners", "Error while fetching owners.", exception); + return StatusCode(500, "Internal server error"); + } + } + + /// + /// Builds GraphQL variables for the owner lookup. + /// + internal static Dictionary BuildQueryVariables(GetOwnersRequest request, ClaimsPrincipal user) + { + List> predicates = BuildFilterPredicates(request); + if (ShouldRestrictToEditableOwners(user)) + { + predicates.Add(BuildInExpression("id", JwtClaimParser.ExtractIntClaimValues(user.Claims, "x-hasura-editable-owners"))); + } + + Dictionary whereClause = predicates.Count switch + { + 0 => [], + 1 => predicates[0], + _ => new Dictionary { ["_and"] = predicates } + }; + return new Dictionary { ["where"] = whereClause }; + } + + /// + /// Converts an owner to the REST response shape. + /// + internal static GetOwnerResponse ToResponse(FwoOwner owner) + { + return new GetOwnerResponse + { + Id = owner.Id, + Name = owner.Name, + AppIdExternal = owner.ExtAppId, + Type = IsStandardOwner(owner.ExtAppId) ? StandardOwnerType : InfrastructureOwnerType + }; + } + + private static List> BuildFilterPredicates(GetOwnersRequest request) + { + List> predicates = []; + AddEqualsPredicate(predicates, "id", request.OwnerId); + AddEqualsPredicate(predicates, "owner_lifecycle_state_id", request.OwnerLifeCycleStateId); + AddEqualsPredicate(predicates, "active", request.Active); + AddWildcardPredicate(predicates, "name", request.Name); + AddWildcardPredicate(predicates, "app_id_external", request.AppIdExternal); + return predicates; + } + + private static void AddEqualsPredicate(List> predicates, string fieldName, object? value) + { + if (value is not null) + { + predicates.Add(new Dictionary { [fieldName] = new Dictionary { ["_eq"] = value } }); + } + } + + private static void AddWildcardPredicate(List> predicates, string fieldName, string? value) + { + if (!string.IsNullOrWhiteSpace(value)) + { + predicates.Add(new Dictionary { [fieldName] = new Dictionary { ["_ilike"] = BuildLikePattern(value) } }); + } + } + + private static Dictionary BuildInExpression(string fieldName, List values) + { + return new Dictionary { [fieldName] = new Dictionary { ["_in"] = values } }; + } + + private static bool ShouldRestrictToEditableOwners(ClaimsPrincipal user) + { + return user.IsInRole(Roles.Modeller) && !user.IsInRole(Roles.Admin) && !user.IsInRole(Roles.Auditor); + } + + private static bool IsStandardOwner(string? appIdExternal) + { + return appIdExternal?.Contains("app", StringComparison.OrdinalIgnoreCase) == true; + } + + private static string BuildLikePattern(string value) + { + string trimmedValue = value.Trim(); + string pattern = trimmedValue.Replace('*', '%').Replace('?', '_'); + bool hasWildcard = pattern.Contains('%') || pattern.Contains('_'); + return hasWildcard ? pattern : $"%{pattern}%"; + } +} diff --git a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs new file mode 100644 index 0000000000..a1b6954691 --- /dev/null +++ b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace FWO.Middleware.Server.Requests; + +/// +/// Represents owner lookup filters for the owners/get endpoint. +/// +public sealed class GetOwnersRequest +{ + /// + /// Gets or sets the optional owner database id filter. + /// + [JsonPropertyName("ownerId")] + public int? OwnerId { get; set; } + + /// + /// Gets or sets the optional owner lifecycle state id filter. + /// + [JsonPropertyName("ownerLifecycleStateId")] + public int? OwnerLifeCycleStateId { get; set; } + + /// + /// Gets or sets the optional owner active flag filter. + /// + [JsonPropertyName("active")] + public bool? Active { get; set; } + + /// + /// Gets or sets the optional owner name filter. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// Gets or sets the optional external application id filter. + /// + [JsonPropertyName("appIdExternal")] + public string? AppIdExternal { get; set; } +} diff --git a/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs new file mode 100644 index 0000000000..6cc7ae66e4 --- /dev/null +++ b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace FWO.Middleware.Server.Responses; + +/// +/// Represents an owner returned by the owners/get endpoint. +/// +public sealed class GetOwnerResponse +{ + /// + /// Gets or sets the owner database id. + /// + [JsonPropertyName("id")] + public int Id { get; set; } + + /// + /// Gets or sets the owner name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the external application id. + /// + [JsonPropertyName("appIdExternal")] + public string? AppIdExternal { get; set; } + + /// + /// Gets or sets the owner type. + /// + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; +} diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs new file mode 100644 index 0000000000..189cbc4b52 --- /dev/null +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -0,0 +1,179 @@ +using System.Reflection; +using System.Security.Claims; +using System.Text.Json; +using FWO.Api.Client; +using FWO.Api.Client.Queries; +using FWO.Basics; +using FWO.Data; +using FWO.Middleware.Server.Controllers; +using FWO.Middleware.Server.Requests; +using FWO.Middleware.Server.Responses; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using NUnit.Framework; + +namespace FWO.Test +{ + [TestFixture] + internal class OwnersControllerTest + { + [Test] + public void GetUsesApiOwnersRoute() + { + RouteAttribute[] controllerRoutes = typeof(OwnersController).GetCustomAttributes().ToArray(); + MethodInfo getMethod = typeof(OwnersController).GetMethod(nameof(OwnersController.Get))!; + HttpPostAttribute? httpPost = getMethod.GetCustomAttribute(); + + Assert.That(controllerRoutes.Select(route => route.Template), Is.EquivalentTo(new[] { "api/owners" })); + Assert.That(httpPost?.Template, Is.EqualTo("get")); + } + + [Test] + public void GetAllowsAuditorAdminAndModeller() + { + MethodInfo getMethod = typeof(OwnersController).GetMethod(nameof(OwnersController.Get))!; + AuthorizeAttribute? authorize = getMethod.GetCustomAttribute(); + + Assert.That(authorize, Is.Not.Null); + Assert.That(authorize!.Roles, Is.EqualTo($"{Roles.Auditor}, {Roles.Admin}, {Roles.Modeller}")); + } + + [Test] + public async Task GetBuildsAndCombinedFilterPredicates() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + await controller.Get(new GetOwnersRequest + { + OwnerId = 42, + OwnerLifeCycleStateId = 3, + Active = true, + Name = "Accounting", + AppIdExternal = "APP-42" + }); + + Assert.That(apiConnection.Query, Is.EqualTo(OwnerQueries.getOwnersFiltered)); + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Contain("\"id\":{\"_eq\":42}")); + Assert.That(variables, Does.Contain("\"owner_lifecycle_state_id\":{\"_eq\":3}")); + Assert.That(variables, Does.Contain("\"active\":{\"_eq\":true}")); + Assert.That(variables, Does.Contain("\"name\":{\"_ilike\":\"%Accounting%\"}")); + Assert.That(variables, Does.Contain("\"app_id_external\":{\"_ilike\":\"%APP-42%\"}")); + Assert.That(variables, Does.Contain("\"_and\"")); + } + + [Test] + public async Task GetConvertsWildcardFiltersToLikePatterns() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + await controller.Get(new GetOwnersRequest + { + Name = "Finance*", + AppIdExternal = "APP-?" + }); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Contain("\"name\":{\"_ilike\":\"Finance%\"}")); + Assert.That(variables, Does.Contain("\"app_id_external\":{\"_ilike\":\"APP-_\"}")); + } + + [Test] + public async Task GetRestrictsModellerToEditableOwnerIds() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController( + apiConnection, + PrincipalWithRolesAndClaims([Roles.Modeller], new Claim("x-hasura-editable-owners", "{7,8}"))); + + await controller.Get(new GetOwnersRequest()); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Contain("\"id\":{\"_in\":[7,8]}")); + } + + [Test] + public async Task GetDoesNotRestrictAdminWhoAlsoHasModellerRole() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController( + apiConnection, + PrincipalWithRolesAndClaims([Roles.Admin, Roles.Modeller], new Claim("x-hasura-editable-owners", "{7,8}"))); + + await controller.Get(new GetOwnersRequest()); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Not.Contain("\"_in\"")); + } + + [Test] + public async Task GetMapsOwnerResponseType() + { + OwnersApiConnection apiConnection = new() + { + Owners = + [ + new FwoOwner { Id = 1, Name = "Application", ExtAppId = "APP-0001" }, + new FwoOwner { Id = 2, Name = "Network", ExtAppId = "COM-0002" }, + new FwoOwner { Id = 3, Name = "Empty" } + ] + }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); + + ActionResult> result = await controller.Get(new GetOwnersRequest()); + + OkObjectResult okResult = (OkObjectResult)result.Result!; + List owners = (List)okResult.Value!; + Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure" })); + } + + private static OwnersController CreateController(ApiConnection apiConnection, ClaimsPrincipal user) + { + return new OwnersController(apiConnection) + { + ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = user } + } + }; + } + + private static ClaimsPrincipal PrincipalWithRoles(params string[] roles) + { + return PrincipalWithRolesAndClaims(roles, []); + } + + private static ClaimsPrincipal PrincipalWithRolesAndClaims(IEnumerable roles, params Claim[] claims) + { + IEnumerable roleClaims = roles.Select(role => new Claim(ClaimTypes.Role, role)); + ClaimsIdentity identity = new(roleClaims.Concat(claims), "test", ClaimTypes.Name, ClaimTypes.Role); + return new ClaimsPrincipal(identity); + } + + private static string SerializeVariables(object? variables) + { + return JsonSerializer.Serialize(variables); + } + + private sealed class OwnersApiConnection : SimulatedApiConnection + { + public string Query { get; private set; } = string.Empty; + public object? Variables { get; private set; } + public List Owners { get; set; } = []; + + public override Task SendQueryAsync( + string query, + object? variables = null, + string? operationName = null, + QueryChunkingOptions? chunkingOptions = null) + { + Query = query; + Variables = variables; + return Task.FromResult((QueryResponseType)(object)Owners); + } + } + } +} From 8b174d3e4349db30b4705089493dd6a43eaec65a Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Fri, 19 Jun 2026 17:41:34 +0200 Subject: [PATCH 02/14] docs --- .../FWO.Middleware.Server/Controllers/OwnersController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index 4a73bdbae1..d1c394dca3 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -27,6 +27,9 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase /// Returns all owners visible to the caller with optional AND-combined filters. /// /// + /// Requires one of the roles admin, auditor, or modeller. + /// Modeller callers only receive owners listed in their x-hasura-editable-owners JWT claim. + /// /// Example request bodies: /// /// {} @@ -53,6 +56,8 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase [HttpPost("get")] [Consumes("application/json")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] [Authorize(Roles = $"{Roles.Auditor}, {Roles.Admin}, {Roles.Modeller}")] public async Task>> Get([FromBody] GetOwnersRequest? request) From f191e6418f8d2de720cda8b77bfe8c9304b1ea97 Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Fri, 19 Jun 2026 18:29:20 +0200 Subject: [PATCH 03/14] add state name --- .../owner/fragments/ownerDetails.graphql | 5 +++++ roles/lib/files/FWO.Data/FwoOwner.cs | 5 +++++ roles/lib/files/FWO.Report/ReportOwners.cs | 20 +++++++++++++++++++ roles/tests-unit/files/FWO.Test/ExportTest.cs | 14 ++++++++----- .../files/FWO.Test/SimulatedUserConfig.cs | 1 + 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/roles/common/files/fwo-api-calls/owner/fragments/ownerDetails.graphql b/roles/common/files/fwo-api-calls/owner/fragments/ownerDetails.graphql index f17218b695..afe893d2e1 100644 --- a/roles/common/files/fwo-api-calls/owner/fragments/ownerDetails.graphql +++ b/roles/common/files/fwo-api-calls/owner/fragments/ownerDetails.graphql @@ -14,6 +14,11 @@ fragment ownerDetails on owner { additional_info criticality owner_lifecycle_state_id + owner_lifecycle_state { + id + name + active_state + } active import_source common_service_possible diff --git a/roles/lib/files/FWO.Data/FwoOwner.cs b/roles/lib/files/FWO.Data/FwoOwner.cs index b801dd9f7d..4fddd6147a 100644 --- a/roles/lib/files/FWO.Data/FwoOwner.cs +++ b/roles/lib/files/FWO.Data/FwoOwner.cs @@ -24,6 +24,9 @@ public class FwoOwner : FwoOwnerBase [JsonProperty("owner_lifecycle_state_id"), JsonPropertyName("owner_lifecycle_state_id")] public int? OwnerLifeCycleStateId { get; set; } + [JsonProperty("owner_lifecycle_state"), JsonPropertyName("owner_lifecycle_state")] + public OwnerLifeCycleState? OwnerLifeCycleState { get; set; } + [JsonProperty("active"), JsonPropertyName("active")] public bool Active { get; set; } = true; @@ -74,6 +77,8 @@ public FwoOwner(FwoOwner owner) : base(owner) RecertCheckParamString = owner.RecertCheckParamString; AdditionalInfo = owner.AdditionalInfo == null ? null : new Dictionary(owner.AdditionalInfo); Criticality = owner.Criticality; + OwnerLifeCycleStateId = owner.OwnerLifeCycleStateId; + OwnerLifeCycleState = owner.OwnerLifeCycleState == null ? null : new OwnerLifeCycleState(owner.OwnerLifeCycleState); Active = owner.Active; ImportSource = owner.ImportSource; CommSvcPossible = owner.CommSvcPossible; diff --git a/roles/lib/files/FWO.Report/ReportOwners.cs b/roles/lib/files/FWO.Report/ReportOwners.cs index 22bd229a1d..15abc0241b 100644 --- a/roles/lib/files/FWO.Report/ReportOwners.cs +++ b/roles/lib/files/FWO.Report/ReportOwners.cs @@ -17,6 +17,8 @@ public class ReportOwners(DynGraphqlQuery query, UserConfig userConfig, ReportTy { private Dictionary ownerLifeCycleStates = []; private List ownerResponsibleTypes = []; + private string OwnerLifeCycleStateIdHeader => $"{userConfig.GetText("owner_lc_state")} {userConfig.GetText("id")}"; + private string OwnerLifeCycleStateNameHeader => $"{userConfig.GetText("owner_lc_state")} {userConfig.GetText("name")}"; private IEnumerable OrderedOwners => ReportData.OwnerData .Select(ownerReport => ownerReport.Owner) .OrderBy(owner => owner.ExtAppId ?? "") @@ -73,6 +75,8 @@ private void AppendOwnerDataHeadlineHtml(ref StringBuilder report) report.AppendLine($"{userConfig.GetText("name")}"); report.AppendLine($"{userConfig.GetText("criticality")}"); report.AppendLine($"{userConfig.GetText("state")}"); + report.AppendLine($"{OwnerLifeCycleStateIdHeader}"); + report.AppendLine($"{OwnerLifeCycleStateNameHeader}"); foreach (OwnerResponsibleType type in ownerResponsibleTypes) { report.AppendLine($"{EncodeHtml(type.Name)}"); @@ -87,6 +91,8 @@ private void AppendOwnerDataHeadlineCsv(ref StringBuilder report) report.Append(OutputCsv(userConfig.GetText("name"))); report.Append(OutputCsv(userConfig.GetText("criticality"))); report.Append(OutputCsv(userConfig.GetText("state"))); + report.Append(OutputCsv(OwnerLifeCycleStateIdHeader)); + report.Append(OutputCsv(OwnerLifeCycleStateNameHeader)); foreach (OwnerResponsibleType type in ownerResponsibleTypes) { report.Append(OutputCsv(type.Name)); @@ -102,6 +108,8 @@ private void AppendOwnerDataHtml(ref StringBuilder report, FwoOwner owner) report.AppendLine($"{EncodeHtml(owner.Name)}"); report.AppendLine($"{EncodeHtml(owner.Criticality)}"); report.AppendLine($"{EncodeHtml(GetOwnerState(owner))}"); + report.AppendLine($"{EncodeHtml(owner.OwnerLifeCycleStateId?.ToString())}"); + report.AppendLine($"{EncodeHtml(GetOwnerStateName(owner))}"); foreach (OwnerResponsibleType type in ownerResponsibleTypes) { report.AppendLine($"{FormatHtmlCell(OwnerRecertDisplay.FormatResponsibles(owner, type.Id, "\n"))}"); @@ -116,6 +124,8 @@ private void AppendOwnerDataCsv(ref StringBuilder report, FwoOwner owner) report.Append(OutputCsv(owner.Name)); report.Append(OutputCsv(owner.Criticality)); report.Append(OutputCsv(GetOwnerState(owner))); + report.Append(OutputCsv(owner.OwnerLifeCycleStateId?.ToString())); + report.Append(OutputCsv(GetOwnerStateName(owner))); foreach (OwnerResponsibleType type in ownerResponsibleTypes) { report.Append(OutputCsv(OwnerRecertDisplay.FormatResponsibles(owner, type.Id, "; "))); @@ -134,6 +144,16 @@ private string GetOwnerState(FwoOwner owner) return ""; } + private string GetOwnerStateName(FwoOwner owner) + { + if (!string.IsNullOrWhiteSpace(owner.OwnerLifeCycleState?.Name)) + { + return owner.OwnerLifeCycleState.Name; + } + + return GetOwnerState(owner); + } + private static string GetAdditionalInfo(FwoOwner owner, string separator = "\n") { return owner.AdditionalInfo == null diff --git a/roles/tests-unit/files/FWO.Test/ExportTest.cs b/roles/tests-unit/files/FWO.Test/ExportTest.cs index 9ed90ea5ff..04112b9d6e 100644 --- a/roles/tests-unit/files/FWO.Test/ExportTest.cs +++ b/roles/tests-unit/files/FWO.Test/ExportTest.cs @@ -581,6 +581,7 @@ public void OwnersGenerateCsvAndHtml() Name = "Owner One", Criticality = "High", OwnerLifeCycleStateId = 1, + OwnerLifeCycleState = new OwnerLifeCycleState() { Id = 1, Name = "Active", ActiveState = true }, AdditionalInfo = new Dictionary() { { "department", "IT" }, { "region", "EU" } }, OwnerResponsibles = [ @@ -601,11 +602,14 @@ public void OwnersGenerateCsvAndHtml() string csv = report.ExportToCsv(); string html = RemoveLinebreaks(report.ExportToHtml()); - - StringAssert.Contains("\"Id\",\"Name\",\"Criticality\",\"State\",\"Main\",\"Backup\",\"Additional Info\",", csv); - StringAssert.Contains("\"APP-1\",\"Owner One\",\"High\",\"Active\",\"user1\",\"user2\",\"department: IT; region: EU\",", csv); - StringAssert.Contains("IdNameCriticalityStateMainBackupAdditional Info", html); - StringAssert.Contains("APP-1Owner OneHighActiveuser1user2department: IT
region: EU", html); + string json = report.ExportToJson(); + + StringAssert.Contains("\"Id\",\"Name\",\"Criticality\",\"State\",\"Production state Id\",\"Production state Name\",\"Main\",\"Backup\",\"Additional Info\",", csv); + StringAssert.Contains("\"APP-1\",\"Owner One\",\"High\",\"Active\",\"1\",\"Active\",\"user1\",\"user2\",\"department: IT; region: EU\",", csv); + StringAssert.Contains("IdNameCriticalityStateProduction state IdProduction state NameMainBackupAdditional Info", html); + StringAssert.Contains("APP-1Owner OneHighActive1Activeuser1user2department: IT
region: EU", html); + StringAssert.Contains("\"owner_lifecycle_state_id\": 1", json); + StringAssert.Contains("\"name\": \"Active\"", json); } private static void SetPrivateField(object target, string fieldName, T value) diff --git a/roles/tests-unit/files/FWO.Test/SimulatedUserConfig.cs b/roles/tests-unit/files/FWO.Test/SimulatedUserConfig.cs index f8b815a41b..7611892271 100644 --- a/roles/tests-unit/files/FWO.Test/SimulatedUserConfig.cs +++ b/roles/tests-unit/files/FWO.Test/SimulatedUserConfig.cs @@ -274,6 +274,7 @@ internal class SimulatedUserConfig : UserConfig {"U9041","Are you sure you want to close @@COUNT@@ ticket(s) as done?" }, {"state","State" }, {"criticality","Criticality" }, + {"owner_lc_state","Production state" }, {"additional_info","Additional Info" }, {"active","Active" }, {"inactive","Inactive" }, From ed9dcef31a4dcf22b2356b1dcc33c05b601d2bc2 Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Sun, 21 Jun 2026 12:23:40 +0200 Subject: [PATCH 04/14] adding details --- .../Controllers/OwnersController.cs | 60 +++++++- .../Requests/GetOwnersRequest.cs | 7 + .../Responses/GetOwnerResponse.cs | 144 ++++++++++++++++++ .../files/FWO.Test/OwnersControllerTest.cs | 107 +++++++++++++ 4 files changed, 311 insertions(+), 7 deletions(-) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index d1c394dca3..c03859687b 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -43,13 +43,18 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase /// /// {"name":"Finance*","appIdExternal":"APP-?"} /// + /// + /// {"showDetails":true} + /// /// Example response: /// /// [ - /// {"id":42,"name":"Finance Portal","appIdExternal":"APP-4711","type":"standard"}, - /// {"id":43,"name":"Finance Network","appIdExternal":"NET-4712","type":"infrastructure"} + /// {"id":42,"name":"Finance Portal","appIdExternal":"APP-4711","type":"standard","ownerLifecycleState":{"id":1,"name":"Active"}}, + /// {"id":43,"name":"Finance Network","appIdExternal":"NET-4712","type":"infrastructure","ownerLifecycleState":null} /// ] /// + /// Set showDetails to true to additionally return all owner fields (tenant id, recertification + /// data, criticality, lifecycle state id, additional info, etc.). By default only the core fields are returned. /// The name and appIdExternal filters are case-insensitive and accept * for any /// character sequence and ? for a single character. Plain text without wildcards is matched as a contains search. ///
@@ -64,11 +69,12 @@ public async Task>> Get([FromBody] GetOwners { try { + GetOwnersRequest effectiveRequest = request ?? new GetOwnersRequest(); List owners = await apiConnection.SendQueryAsync>( OwnerQueries.getOwnersFiltered, - BuildQueryVariables(request ?? new GetOwnersRequest(), User)) ?? []; + BuildQueryVariables(effectiveRequest, User)) ?? []; - return Ok(owners.Select(ToResponse).ToList()); + return Ok(owners.Select(owner => ToResponse(owner, effectiveRequest.ShowDetails)).ToList()); } catch (Exception exception) { @@ -100,15 +106,55 @@ internal static Dictionary BuildQueryVariables(GetOwnersRequest /// /// Converts an owner to the REST response shape. /// - internal static GetOwnerResponse ToResponse(FwoOwner owner) + /// The owner to convert. + /// Whether to include all owner detail fields. + internal static GetOwnerResponse ToResponse(FwoOwner owner, bool showDetails) { - return new GetOwnerResponse + GetOwnerResponse response = new() { Id = owner.Id, Name = owner.Name, AppIdExternal = owner.ExtAppId, - Type = IsStandardOwner(owner.ExtAppId) ? StandardOwnerType : InfrastructureOwnerType + Type = IsStandardOwner(owner.ExtAppId) ? StandardOwnerType : InfrastructureOwnerType, + OwnerLifecycleState = owner.OwnerLifeCycleState is null + ? null + : new OwnerLifecycleStateResponse + { + Id = owner.OwnerLifeCycleState.Id, + Name = owner.OwnerLifeCycleState.Name + } }; + + if (showDetails) + { + AddDetails(response, owner); + } + + return response; + } + + /// + /// Populates the full set of owner fields on the response. + /// + private static void AddDetails(GetOwnerResponse response, FwoOwner owner) + { + response.IsDefault = owner.IsDefault; + response.TenantId = owner.TenantId; + response.RecertInterval = owner.RecertInterval; + response.LastRecertCheck = owner.LastRecertCheck; + response.RecertCheckParams = owner.RecertCheckParamString; + response.Criticality = owner.Criticality; + response.OwnerLifecycleStateId = owner.OwnerLifeCycleStateId; + response.Active = owner.Active; + response.ImportSource = owner.ImportSource; + response.CommonServicePossible = owner.CommSvcPossible; + response.LastRecertified = owner.LastRecertified; + response.LastRecertifier = owner.LastRecertifierId; + response.LastRecertifierDn = owner.LastRecertifierDn; + response.NextRecertDate = owner.NextRecertDate; + response.RecertActive = owner.RecertActive; + response.DecommDate = owner.DecommDate; + response.AdditionalInfo = owner.AdditionalInfo; } private static List> BuildFilterPredicates(GetOwnersRequest request) diff --git a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs index a1b6954691..dc3fba87da 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs @@ -36,4 +36,11 @@ public sealed class GetOwnersRequest /// [JsonPropertyName("appIdExternal")] public string? AppIdExternal { get; set; } + + /// + /// Gets or sets a value indicating whether all owner fields should be returned. + /// When false (default) only the core fields are returned. + /// + [JsonPropertyName("showDetails")] + public bool ShowDetails { get; set; } } diff --git a/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs index 6cc7ae66e4..59da1a3917 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs @@ -4,6 +4,7 @@ namespace FWO.Middleware.Server.Responses; /// /// Represents an owner returned by the owners/get endpoint. +/// Detail fields are only populated when showDetails is requested. /// public sealed class GetOwnerResponse { @@ -30,4 +31,147 @@ public sealed class GetOwnerResponse /// [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; + + /// + /// Gets or sets the owner lifecycle state. + /// + [JsonPropertyName("ownerLifecycleState")] + public OwnerLifecycleStateResponse? OwnerLifecycleState { get; set; } + + /// + /// Gets or sets a value indicating whether the owner is the default owner. + /// + [JsonPropertyName("isDefault")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? IsDefault { get; set; } + + /// + /// Gets or sets the tenant id. + /// + [JsonPropertyName("tenantId")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? TenantId { get; set; } + + /// + /// Gets or sets the recertification interval in days. + /// + [JsonPropertyName("recertInterval")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? RecertInterval { get; set; } + + /// + /// Gets or sets the timestamp of the last recertification check. + /// + [JsonPropertyName("lastRecertCheck")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? LastRecertCheck { get; set; } + + /// + /// Gets or sets the recertification check parameters. + /// + [JsonPropertyName("recertCheckParams")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? RecertCheckParams { get; set; } + + /// + /// Gets or sets the owner criticality. + /// + [JsonPropertyName("criticality")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Criticality { get; set; } + + /// + /// Gets or sets the owner lifecycle state id. + /// + [JsonPropertyName("ownerLifecycleStateId")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? OwnerLifecycleStateId { get; set; } + + /// + /// Gets or sets a value indicating whether the owner is active. + /// + [JsonPropertyName("active")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Active { get; set; } + + /// + /// Gets or sets the import source. + /// + [JsonPropertyName("importSource")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ImportSource { get; set; } + + /// + /// Gets or sets a value indicating whether common services are possible. + /// + [JsonPropertyName("commonServicePossible")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? CommonServicePossible { get; set; } + + /// + /// Gets or sets the timestamp of the last recertification. + /// + [JsonPropertyName("lastRecertified")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? LastRecertified { get; set; } + + /// + /// Gets or sets the id of the last recertifier. + /// + [JsonPropertyName("lastRecertifier")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? LastRecertifier { get; set; } + + /// + /// Gets or sets the distinguished name of the last recertifier. + /// + [JsonPropertyName("lastRecertifierDn")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? LastRecertifierDn { get; set; } + + /// + /// Gets or sets the next recertification date. + /// + [JsonPropertyName("nextRecertDate")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? NextRecertDate { get; set; } + + /// + /// Gets or sets a value indicating whether recertification is active. + /// + [JsonPropertyName("recertActive")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? RecertActive { get; set; } + + /// + /// Gets or sets the decommission date. + /// + [JsonPropertyName("decommDate")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? DecommDate { get; set; } + + /// + /// Gets or sets additional info stored for the owner. + /// + [JsonPropertyName("additionalInfo")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? AdditionalInfo { get; set; } +} + +/// +/// Represents the lifecycle state of an owner. +/// +public sealed class OwnerLifecycleStateResponse +{ + /// + /// Gets or sets the lifecycle state id. + /// + [JsonPropertyName("id")] + public int Id { get; set; } + + /// + /// Gets or sets the lifecycle state name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 189cbc4b52..078db1f508 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -130,6 +130,113 @@ public async Task GetMapsOwnerResponseType() Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure" })); } + [Test] + public async Task GetMapsOwnerLifecycleState() + { + OwnersApiConnection apiConnection = new() + { + Owners = + [ + new FwoOwner + { + Id = 1, + Name = "Application", + ExtAppId = "APP-0001", + OwnerLifeCycleState = new OwnerLifeCycleState { Id = 5, Name = "Active", ActiveState = true } + }, + new FwoOwner { Id = 2, Name = "Network", ExtAppId = "COM-0002" } + ] + }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); + + ActionResult> result = await controller.Get(new GetOwnersRequest()); + + OkObjectResult okResult = (OkObjectResult)result.Result!; + List owners = (List)okResult.Value!; + Assert.Multiple(() => + { + Assert.That(owners[0].OwnerLifecycleState, Is.Not.Null); + Assert.That(owners[0].OwnerLifecycleState!.Id, Is.EqualTo(5)); + Assert.That(owners[0].OwnerLifecycleState!.Name, Is.EqualTo("Active")); + Assert.That(owners[1].OwnerLifecycleState, Is.Null); + }); + } + + [Test] + public async Task GetOmitsDetailFieldsByDefault() + { + OwnersApiConnection apiConnection = new() + { + Owners = + [ + new FwoOwner { Id = 1, Name = "Application", ExtAppId = "APP-0001", TenantId = 7, Criticality = "high", Active = false } + ] + }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); + + ActionResult> result = await controller.Get(new GetOwnersRequest()); + + OkObjectResult okResult = (OkObjectResult)result.Result!; + List owners = (List)okResult.Value!; + Assert.Multiple(() => + { + Assert.That(owners[0].TenantId, Is.Null); + Assert.That(owners[0].Criticality, Is.Null); + Assert.That(owners[0].Active, Is.Null); + }); + } + + [Test] + public async Task GetReturnsDetailFieldsWhenRequested() + { + OwnersApiConnection apiConnection = new() + { + Owners = + [ + new FwoOwner + { + Id = 1, + Name = "Application", + ExtAppId = "APP-0001", + IsDefault = true, + TenantId = 7, + RecertInterval = 365, + Criticality = "high", + OwnerLifeCycleStateId = 5, + Active = false, + ImportSource = "import", + CommSvcPossible = true, + LastRecertifierId = 11, + LastRecertifierDn = "cn=user", + RecertActive = true, + AdditionalInfo = new Dictionary { ["key"] = "value" } + } + ] + }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); + + ActionResult> result = await controller.Get(new GetOwnersRequest { ShowDetails = true }); + + OkObjectResult okResult = (OkObjectResult)result.Result!; + GetOwnerResponse owner = ((List)okResult.Value!)[0]; + Assert.Multiple(() => + { + Assert.That(owner.IsDefault, Is.True); + Assert.That(owner.TenantId, Is.EqualTo(7)); + Assert.That(owner.RecertInterval, Is.EqualTo(365)); + Assert.That(owner.Criticality, Is.EqualTo("high")); + Assert.That(owner.OwnerLifecycleStateId, Is.EqualTo(5)); + Assert.That(owner.Active, Is.False); + Assert.That(owner.ImportSource, Is.EqualTo("import")); + Assert.That(owner.CommonServicePossible, Is.True); + Assert.That(owner.LastRecertifier, Is.EqualTo(11)); + Assert.That(owner.LastRecertifierDn, Is.EqualTo("cn=user")); + Assert.That(owner.RecertActive, Is.True); + Assert.That(owner.AdditionalInfo, Is.Not.Null); + Assert.That(owner.AdditionalInfo!["key"], Is.EqualTo("value")); + }); + } + private static OwnersController CreateController(ApiConnection apiConnection, ClaimsPrincipal user) { return new OwnersController(apiConnection) From 79bcc0f179f840c73acc95392e56c4c117b63b4b Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Sun, 21 Jun 2026 12:48:03 +0200 Subject: [PATCH 05/14] nullable showDetails --- .../FWO.Middleware.Server/Controllers/OwnersController.cs | 2 +- .../files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index c03859687b..36a6219eb1 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -74,7 +74,7 @@ public async Task>> Get([FromBody] GetOwners OwnerQueries.getOwnersFiltered, BuildQueryVariables(effectiveRequest, User)) ?? []; - return Ok(owners.Select(owner => ToResponse(owner, effectiveRequest.ShowDetails)).ToList()); + return Ok(owners.Select(owner => ToResponse(owner, effectiveRequest.ShowDetails == true)).ToList()); } catch (Exception exception) { diff --git a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs index dc3fba87da..6bf31e3393 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs @@ -39,8 +39,8 @@ public sealed class GetOwnersRequest /// /// Gets or sets a value indicating whether all owner fields should be returned. - /// When false (default) only the core fields are returned. + /// When null or false (default) only the core fields are returned. /// [JsonPropertyName("showDetails")] - public bool ShowDetails { get; set; } + public bool? ShowDetails { get; set; } } From 7a9cb349019b1976975e5923a9bc0cb9a65d046a Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Sun, 21 Jun 2026 12:53:30 +0200 Subject: [PATCH 06/14] filter out inactive state owners by default --- .../Controllers/OwnersController.cs | 24 +++++++++++++++++ .../Requests/GetOwnersRequest.cs | 8 ++++++ .../files/FWO.Test/OwnersControllerTest.cs | 26 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index 36a6219eb1..4c51643f98 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -55,6 +55,8 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase /// /// Set showDetails to true to additionally return all owner fields (tenant id, recertification /// data, criticality, lifecycle state id, additional info, etc.). By default only the core fields are returned. + /// By default owners with an inactive lifecycle state are excluded; set showOnlyActiveState to + /// false to also include them. Owners without any lifecycle state are always returned. /// The name and appIdExternal filters are case-insensitive and accept * for any /// character sequence and ? for a single character. Plain text without wildcards is matched as a contains search. /// @@ -165,9 +167,31 @@ private static List> BuildFilterPredicates(GetOwnersR AddEqualsPredicate(predicates, "active", request.Active); AddWildcardPredicate(predicates, "name", request.Name); AddWildcardPredicate(predicates, "app_id_external", request.AppIdExternal); + AddActiveStatePredicate(predicates, request.ShowOnlyActiveState); return predicates; } + /// + /// Excludes owners whose lifecycle state is inactive, while keeping owners without a lifecycle state. + /// Applied by default unless is explicitly false. + /// + private static void AddActiveStatePredicate(List> predicates, bool? showOnlyActiveState) + { + if (showOnlyActiveState == false) + { + return; + } + + predicates.Add(new Dictionary + { + ["_or"] = new List> + { + new() { ["owner_lifecycle_state"] = new Dictionary { ["active_state"] = new Dictionary { ["_eq"] = true } } }, + new() { ["owner_lifecycle_state_id"] = new Dictionary { ["_is_null"] = true } } + } + }); + } + private static void AddEqualsPredicate(List> predicates, string fieldName, object? value) { if (value is not null) diff --git a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs index 6bf31e3393..63236daa17 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs @@ -43,4 +43,12 @@ public sealed class GetOwnersRequest /// [JsonPropertyName("showDetails")] public bool? ShowDetails { get; set; } + + /// + /// Gets or sets a value indicating whether owners with an inactive lifecycle state should be excluded. + /// When null or true (default) owners whose lifecycle state is inactive are filtered out; + /// owners without a lifecycle state are kept. Set to false to also include owners with an inactive state. + /// + [JsonPropertyName("showOnlyActiveState")] + public bool? ShowOnlyActiveState { get; set; } } diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 078db1f508..6dd32bbab7 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -64,6 +64,32 @@ await controller.Get(new GetOwnersRequest Assert.That(variables, Does.Contain("\"_and\"")); } + [Test] + public async Task GetExcludesInactiveLifecycleStateByDefault() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + await controller.Get(new GetOwnersRequest()); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Contain("\"owner_lifecycle_state\":{\"active_state\":{\"_eq\":true}}")); + Assert.That(variables, Does.Contain("\"owner_lifecycle_state_id\":{\"_is_null\":true}")); + Assert.That(variables, Does.Contain("\"_or\"")); + } + + [Test] + public async Task GetIncludesInactiveLifecycleStateWhenDisabled() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + await controller.Get(new GetOwnersRequest { ShowOnlyActiveState = false }); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.That(variables, Does.Not.Contain("active_state")); + } + [Test] public async Task GetConvertsWildcardFiltersToLikePatterns() { From 9ff7d6e0761fae9ec03e2b4c76f5a6006731599f Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Sun, 21 Jun 2026 20:17:18 +0200 Subject: [PATCH 07/14] fix: classify owner type by APP prefix not substring IsStandardOwner used a Contains("app") substring check, so an external app id like "MAPP-1" was wrongly classified as a standard owner. Use a StartsWith("APP") prefix check to match the modelling naming convention (ModellingManagedIdString), and add a regression test case. Co-Authored-By: Claude Opus 4.8 --- .../FWO.Middleware.Server/Controllers/OwnersController.cs | 4 +++- roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index 4c51643f98..cf78ddfa24 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -220,7 +220,9 @@ private static bool ShouldRestrictToEditableOwners(ClaimsPrincipal user) private static bool IsStandardOwner(string? appIdExternal) { - return appIdExternal?.Contains("app", StringComparison.OrdinalIgnoreCase) == true; + // Application owners use the "APP" external-app-id prefix, infrastructure/common owners use "COM" + // (matching the modelling naming convention in ModellingManagedIdString). + return appIdExternal?.StartsWith("APP", StringComparison.OrdinalIgnoreCase) == true; } private static string BuildLikePattern(string value) diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 6dd32bbab7..04de635436 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -144,7 +144,8 @@ public async Task GetMapsOwnerResponseType() [ new FwoOwner { Id = 1, Name = "Application", ExtAppId = "APP-0001" }, new FwoOwner { Id = 2, Name = "Network", ExtAppId = "COM-0002" }, - new FwoOwner { Id = 3, Name = "Empty" } + new FwoOwner { Id = 3, Name = "Empty" }, + new FwoOwner { Id = 4, Name = "ContainsApp", ExtAppId = "MAPP-0004" } ] }; OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); @@ -153,7 +154,9 @@ public async Task GetMapsOwnerResponseType() OkObjectResult okResult = (OkObjectResult)result.Result!; List owners = (List)okResult.Value!; - Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure" })); + // owner type is derived from the external-app-id prefix (APP = standard), not a substring, + // so "MAPP-0004" must not be classified as standard + Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure", "infrastructure" })); } [Test] From 93ccfdc9aec73bcdeb4aca6fa6eccf4ce90b9171 Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Sun, 21 Jun 2026 20:22:14 +0200 Subject: [PATCH 08/14] docs: document owner type derivation Revert the IsStandardOwner logic change and instead document the existing behaviour: owner type is derived from appIdExternal, "standard" when it contains "app" (case-insensitive), "infrastructure" otherwise. Documented in the endpoint Swagger remarks and the response model. Co-Authored-By: Claude Opus 4.8 --- .../FWO.Middleware.Server/Controllers/OwnersController.cs | 7 ++++--- .../FWO.Middleware.Server/Responses/GetOwnerResponse.cs | 3 ++- roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs | 7 ++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index cf78ddfa24..dcb15a8ea2 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -53,6 +53,9 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase /// {"id":43,"name":"Finance Network","appIdExternal":"NET-4712","type":"infrastructure","ownerLifecycleState":null} /// ] /// + /// The type field is derived from the owner's appIdExternal: it is standard when the + /// external app id contains app (case-insensitive), and infrastructure otherwise (including + /// owners without an external app id). /// Set showDetails to true to additionally return all owner fields (tenant id, recertification /// data, criticality, lifecycle state id, additional info, etc.). By default only the core fields are returned. /// By default owners with an inactive lifecycle state are excluded; set showOnlyActiveState to @@ -220,9 +223,7 @@ private static bool ShouldRestrictToEditableOwners(ClaimsPrincipal user) private static bool IsStandardOwner(string? appIdExternal) { - // Application owners use the "APP" external-app-id prefix, infrastructure/common owners use "COM" - // (matching the modelling naming convention in ModellingManagedIdString). - return appIdExternal?.StartsWith("APP", StringComparison.OrdinalIgnoreCase) == true; + return appIdExternal?.Contains("app", StringComparison.OrdinalIgnoreCase) == true; } private static string BuildLikePattern(string value) diff --git a/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs index 59da1a3917..88778920d5 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Responses/GetOwnerResponse.cs @@ -27,7 +27,8 @@ public sealed class GetOwnerResponse public string? AppIdExternal { get; set; } /// - /// Gets or sets the owner type. + /// Gets or sets the owner type, derived from : + /// standard when the external app id contains app (case-insensitive), infrastructure otherwise. /// [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 04de635436..6dd32bbab7 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -144,8 +144,7 @@ public async Task GetMapsOwnerResponseType() [ new FwoOwner { Id = 1, Name = "Application", ExtAppId = "APP-0001" }, new FwoOwner { Id = 2, Name = "Network", ExtAppId = "COM-0002" }, - new FwoOwner { Id = 3, Name = "Empty" }, - new FwoOwner { Id = 4, Name = "ContainsApp", ExtAppId = "MAPP-0004" } + new FwoOwner { Id = 3, Name = "Empty" } ] }; OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); @@ -154,9 +153,7 @@ public async Task GetMapsOwnerResponseType() OkObjectResult okResult = (OkObjectResult)result.Result!; List owners = (List)okResult.Value!; - // owner type is derived from the external-app-id prefix (APP = standard), not a substring, - // so "MAPP-0004" must not be classified as standard - Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure", "infrastructure" })); + Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure" })); } [Test] From b70ce85413a49dd38cee20e1a53501cbade0bf90 Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Mon, 22 Jun 2026 08:23:42 +0200 Subject: [PATCH 09/14] consolidate revision documentation --- documentation/feature-catalogue.md | 3 +- documentation/revision-history-develop.md | 467 ---------------- documentation/revision-history-main.md | 618 ---------------------- documentation/revision-history.md | 517 ++++++++++++++++++ documentation/version-feature-overview.md | 4 +- 5 files changed, 520 insertions(+), 1089 deletions(-) delete mode 100644 documentation/revision-history-develop.md delete mode 100644 documentation/revision-history-main.md create mode 100644 documentation/revision-history.md diff --git a/documentation/feature-catalogue.md b/documentation/feature-catalogue.md index 3571ef1e70..5675076a31 100644 --- a/documentation/feature-catalogue.md +++ b/documentation/feature-catalogue.md @@ -2,8 +2,7 @@ This catalogue lists the features of Firewall Orchestrator grouped by functional area, together with the version in which they were first introduced. It is -compiled from the revision histories -([main](revision-history-main.md), [develop](revision-history-develop.md)). +compiled from the [revision history](revision-history.md). Where a feature was first introduced on the `develop` branch and later shipped in a `main` release, the released (main) version is given as the introduction diff --git a/documentation/revision-history-develop.md b/documentation/revision-history-develop.md deleted file mode 100644 index 266f1ebb40..0000000000 --- a/documentation/revision-history-develop.md +++ /dev/null @@ -1,467 +0,0 @@ -# Firewall Orchestrator Revision History for DEVELOP branch only - -pre-5, a product called IT Security Organizer and was closed source. It was developed starting in 2005. -In 2020 we decided to re-launch a new - -### 6.1.0 - 16.11.2022 DEVELOP -- interactive network analysis prototype in UI -- integrate path analysis to workflow - -### 6.1.1 - 15.12.2022 DEVELOP -- recertification on owner base -- preparation of new task types - -### 6.1.2 - 20.12.2022 DEVELOP -- start of Palo Alto import module - -### 6.1.3 - xx.01.2023 DEVELOP -- enhance recertification - -### 6.1.4 - 27.01.2023 DEVELOP -- prepare delete rule requests - -### 6.2.2 22.03.2023 DEVELOP -- adding last hit of each rule for check point and FortiManager to recertification (report) - -### 6.3.3 09.05.2023 DEVELOP -- new importer module for importing FortiGate directly via FortiOS REST API - -### 6.4.4 19.06.2023 DEVELOP -- CPR8x importer: basic support for inline layers - -### 6.4.5 22.06.2023 DEVELOP -- Fortigate API importer: hotfix NAT rules -- upgrade to hasura API 2.28.0 - -### 6.4.6 23.06.2023 DEVELOP -- new email notification on import changes - -### 6.4.7 26.06.2023 DEVELOP -- hotfix fortiOS importer NAT IP addresses -- fixing issue during ubuntu OS upgrade with ldap -- unifying all buttons in UI - -### 6.4.8 29.06.2023 DEVELOP -- hotfix fortiOS importer: replacing ambiguous import statement - -### 6.4.9 03.07.2023 DEVELOP -- fix sample group role path - -### 6.4.10 07.07.2023 DEVELOP -- fixes in importer change mail notification for encrypted mails -- fixes for report links to objects -- fix template name display issue -- fix UI visibility for fw-admin role (multiple pages) -- UI login page: allow enter as submit -- UI reporting: filter objects in rule report -- adding demo video in github README.MD - -### 6.4.11 10.07.2023 DEVELOP -- bugfix in importer change mail notification for missing mail server config - -### 6.4.12 14.07.2023 DEVELOP -- UI settings: hotfix email port (default 25) was not written to config before -- splitting revision history into develop and main -- installer: supress csharp test results on success - -### 6.4.13 20.07.2023 DEVELOP -- re-login now also with enter key -- fixing help pages (email & importer settings, archive, scheduling) [#2162](https://github.com/CactuseSecurity/firewall-orchestrator/issues/2162) - -### 6.5.0 24.07.2023 DEVELOP -- UI: adding compliance matrix module -- UI: fix browser session persistence causing subscriptions to remain open after user logout; now api connection and web socket are disposed on logout -- API: removing obsolete graphql query repos -- API: upgrading hasura api to 2.30.0 -- installer: replacing deprecated path_to_script option in postgresql_query - -### 6.5.1 24.07.2023 DEVELOP -- New report type Unused Rules - -### 7.0.1 - 28.07.2023 DEVELOP -- Compliance matrix edit fix -- Logout audit logging fix - -### 7.0.2 - 28.07.2023 DEVELOP -- Default templates for new report types - -### 7.1 - 11.08.2023 DEVELOP -- adding tenant network UI -- adding test import via URI in hostname field -- replacing legacy demo data import with standard imported data, closing #2197 (note: only for new installations, an upgrade will not touch the demo data) -- test imports can now be made from file (integrated in UI) -- improve debugging of imports (no errors for missing object parts) - -### 7.1.1 - 15.08.2023 DEVELOP -- fixes upgrade bug on systems without demo data - -### 7.1.2 - 16.08.2023 DEVELOP -- adding Check Point R8x Inform action - -### 7.2 - 21.08.2023 DEVELOP -mostly version update summarizing latest PRs -- UI/API: adding tenant ip filtering beta version (clean-up and optiomazation necessary) -- API: updating hasura to 2.32.0 -- UI: now not showing super managers in RSB all tab -- UI: bug fixes blazor environment settings - - Use production / development based on the build type instead of always using development. - - Do not show detailed errors in production mode. - - Use the custom error page in the production environment. - - Spelling mistake fix -- UI: bug fix jwt expiry - - jwt expiry timer now works as intended - - after the jwt expired no exception can be triggered anymore - -### 7.2.1 - 11.09.2023 DEVELOP -- new settings option for rule ownership mode -### 7.2.2 - 15.09.2023 DEVELOP -- complete re-work: all ip addresses are now internally represented as ranges, including all networks -### 7.2.3 - 29.09.2023 DEVELOP -bugfix release: -- api - upgrade hasura to 2.33.4 -- installer - fix client/server db sort order mismatch (collate) -- adding simulated changes to fwodemodata (fortiate) -- importer - fix in fortiOS importer action field -- UI - - fix settings owner networks editing and displaying - - recert report (and recert page) IP addresses now also simplified like an other reports - - fix broken links in recert page -### 7.2.4 - 04.10.2023 DEVELOP -- new role modeller -- new mechanism for overwriting texts -# 7.2.5 - 05.10.2023 DEVELOP -- importer - - adding more error debugging in CPR8x importer - - adding new network object type 'external-gateway' (for interoperable-dervice in check point) - - fix fortimanager importer: ignore missing negate fields -- middleware & ui: add check for successful publishing dotnet -- middlware: fix upgrade become issue in middleware ldif files -- database: fix postgresql_query module reference - -# 7.2.6 - 06.10.2023 DEVELOP -- importer Checkpoint: adding network object type support for 'CpmiVsClusterNetobj' (for VSX virtual switches) - -# 7.3 - 22.10.2023 DEVELOP -- cleanup unused database views and functions -- first working tenant ip-based filtering - -# 7.3.1 - 26.10.23 DEVELOP -- introducing unfiltered_managements and devices for tenant filtering -- fixing missing api perms fw-admin (management) -- rename management & device tenat_id fields to unfiltered_tenant_id -- fixing UI device selector crashes - -# 7.3.2 - 09.12.2023 DEVELOP -- Modelling first version - -# 7.3.3 - 08.01.2024 DEVELOP -- Moving to vanilla bootstrap css v5.3.2 -- adding extended tenant to device mapping settings (depending on latest bootstrap version) - closes #2280 -- fix for log locking for import process - -# 7.3.4 - 09.01.2024 DEVELOP -- Scheduled import change notification - -# 7.3.5 - 15.01.2024 DEVELOP -- importer log locking fix (only fixing import stopping so far) -- import change notification: - - DB extensions import_control.security_relevant_changes_counter - - removing python import notification - - writing to change counter after import (inpreparation for notification enhancement) -- importer demo tenant device mapping additions (upgrade) -- installer: introducing venv for newer ansible versions and thereby removing version handling - -# 7.3.6 - 23.01.2024 DEVELOP -- common service handling -- fixes credentials when installing without demo data -- fix error with pdf creation on debian testing - -# 8.0.1 - 20.02.2024 DEVELOP -- iconify modelling -- add missing config values - -# 8.0.2 - 11.03.2024 DEVELOP -- first version of NSX import module - -# 8.0.3 - 08.04.2024 DEVELOP -- add maintenance page during upgrade -- sample customizing py script with sample data, closes Installer customizable config (settings) #2275 -- remove log locking from importer due to stalling importer stops -- credentials encryption, closes encrypt passwords and keys #1508 - - breaking change for developer debugging: add the following local file when using -e testkeys=true: - /etc/fworch/secrets/main_key with content "not4production..not4production.." -- add custom (user-defined) fields to import - - cp only so far, other fw types missing - - user-defined fields are not part of reports yet - -# 8.1.1 - 15.04.2024 DEVELOP -- interface request workflow first version - -# 8.1.2 - 22.04.2024 DEVELOP -- encrypt emailPassword in config -- fix demo managements (change import from deactivated to activated - does not affect test managements) -- upgrade to dotnet 8.0 -- adding all imported modelling users to uiuser - -# 8.2.1 - 03.05.2024 DEVELOP -- fix misleading login error message when authorisation is missing - -# 8.2.2 - 14.05.2024 DEVELOP -- fix email credential decryption -- start of Tufin SecureChange integration - -# 8.2.3 - 26.05.2024 DEVELOP -- remove cascading delete for used interfaces -- new properties field in connections - -# 8.2.4 - 19.06.2024 DEVELOP -- owner-filtering for new report type -- new setting for email recipients - -# 8.3.1 - 08.07.2024 DEVELOP -- workflow: external state handling -- fix config value -- remove uniqueness of owner names - -# 8.3.2 - 09.09.2024 DEVELOP -- Added welcome message and settings - -# 8.4.1 - 15.10.24 DEVELOP -- Add missing FK connection.proposed_app_id #2591 - -# 8.4.2 - 17.10.2024 DEVELOP -- external request - -# 8.4.3 - 05.11.2024 DEVELOP -- extra parameters in modelling connection - -# 8.5.1 - 18.11.2024 DEVELOP -- reporting - fixing PDF generation on various platforms -- modelling - fixing AR editing: strict prevention of all area mixing - -# 8.5.2 - 27.11.2024 DEVELOP -- some check point importer fixes - - 4 new colors - - added Internet object - - added voip one more object - -# 8.5.3 - 27.11.2024 DEVELOP -- owner import - make ldap selectable (internal/external) -- small fixes regarding missing config data for two schedulers (daily, app data import) - -# 8.5.4 - 04.12.2024 DEVELOP -- external request: introduce wait cycles - -# 8.6.1 - 12.12.2024 DEVELOP -- external request: introduce locks - -# 8.6.3 - 20.02.2025 DEVELOP -- dns lookup for app server names - -# 8.7.1 - 05.03.2025 DEVELOP -- ldap writepath for groups - demo data (major versions only) -# 8.7.2 - 20.03.2025 DEVELOP -- new config values -- external request: attempt counter - -# 8.8.2 - 07.05.2025 DEVELOP -- displayed state via variance analysis - -# 8.8.3 - 15.05.2025 DEVELOP -- deactivation of connections - -# 8.8.4 - 02.06.2025 DEVELOP -- hotfix for Check Point importer suppor for DLP actions (ask, inform) - -# 8.8.5 - 17.06.2025 DEVELOP -- new enum values for Request Element Field Types -- hotfix change recognition: separate rule changes and "all changes" to make object version handling work properly - -# 8.8.6 - 08.07.2025 DEVELOP -- hotfix CP importer new stm_track: "extended log" and "detailed log" - -# 8.8.8 - 21.08.2025 DEVELOP -- add read-only db user fwo_ro -- also reducing db listener to localhost and other hardening changes - -# 8.8.9 - 27.08.2025 DEVELOP -- prepare tables + settings for owner recert + first throw recert popup -- notification service -- decommissioning of interfaces -- iconification of modelling and related modules -- fix overwrite of objects with interface - -## 8.8.10 - 07.09.2025 DEVELOP -- new report type owner-recertification - -## 8.9.1 - 02.10.2025 DEVELOP -- owner-recertification - -## 8.9.2 - 17.10.2025 DEVELOP -- add ownerLifeCycleState -- add manageable ownerLifeCycleState menu -- fix two modelling ui glitches - -## 8.9.3 - 05.11.2025 DEVELOP -- hotfix missing permissions for app data import in certain constellations - -## 8.9.4 - 09.12.2025 DEVELOP -- bugfix release: common service connection not editable -- new custom scripts for iiq and cmdb import - -## 8.9.5 - 10.12.2025 DEVELOP -- bugfix release: modelling - change planning showed duplicate NA elements for rule delete requests - -## 8.9.6 - 05.01.2026 DEVELOP -- new parameters for notifications - -# 9.0 - 24.01.2026 DEVELOP -A complete 80K lines rework of FWO, including -- database changes to deduplicate rules (rule to gateway mapping now 1:n by introducing rulebase and rulebase_link tables) -- migrating import module from mixed python/pgsql to pure python - -# 9.0.1 - 07.02.2026 DEVELOP -- generalized owner responsibles with configurable responsible types -- add allow_write_access to responsible types to control modelling and recertification - -# 9.0.2 - 10.02.2026 DEVELOP -- importer: call api chunked where needed - -**Breaking changes** -- Due to introduction of venv for all imports, the following steps have to be taken to manually import a config: - -```shell - sudo -u fworch -i - cd importer - source importer-venv/bin/activate - python3 ./import_mgm.py -m xy -fs -d1 -``` - As we now need support for pip, in installations behind url filter, make sure that all sub-domains of "pythonhosted.org" are also allowed. - -- Limiting database listener to localhost for security reasons - -## 9.0.3 - 12.02.2026 DEVELOP -- introduce interface permissions - -## 9.0.4 - 13.02.2026 DEVELOP -- maintenance release with explicit 9.0.4 upgrade step - -# 9.0.5 - 18.02.2026 DEVELOP -- update rule_owner table for REST api -- update import_control to allow flexible tracking of different import types -- create rule_owner mapping for custom_field via button and service/job -- update import_control to allow flexible tracking of different import types - -# 9.0.6 - 20.02.2026 DEVELOP -- add import of time objects - -# 9.0.7 - 25.02.2026 DEVELOP -- add import of time objects -- create changelog_owner table - -# 9.0.8 - 25.02.2026 DEVELOP -- new config value for removed App Server handling - -# 9.0.10 - 28.02.2026 DEVELOP -- new config value for User synchronization in owner data import - -# 9.0.11 - 04.03.2026 DEVELOP -- new config value for requesting only own objects - -# 9.0.12 - 12.03.2026 DEVELOP -- new config values for rule expiry notification - -# 9.0.13 - 12.03.2026 DEVELOP -- mark lifecycle states as active - -# 9.0.14 - 17.03.2026 DEVELOP -- prepare owner decommission notification - -# 9.0.15 - 19.03.2026 DEVELOP -- rename OwnerSourceCustomFieldKey to CustomFieldOwnerKey in config - -# 9.0.16 - 31.03.2026 DEVELOP -- remove not needed stm_owner_mapping_source -- add Full re-initialize of RuleOwner mapping for IP-based rules -- add matched_objects field in rule_owner table for track matched objects - IpBased - -# 9.0.18 - 03.04.2026 DEVELOP -- add new column automatic_only to workflow states - -# 9.0.19 - 09.04.2026 DEVELOP -- add owner additional_info jsonb field including owner edit UI support - -# 9.0.20 - 11.04.2026 DEVELOP -- extend notification handling - -# 9.0.21 - 21.04.2026 DEVELOP -- fix ldap users with special chars not being processed correctly in role handling -- fix empty mail being sent for orphaned rule report -- update dependencies (notably closing mailkit and pytest vuln) -- fix time zone issues in importer - -# 9.0.23 - 27.04.2026 DEVELOP -- enhance notifications by bcc -- add display-only workflow label report column option -- add default template for workflow tickets approved last week -Removed deprecated configuration keys: -- updateRuleOwnerMappingActive -- updateRuleOwnerMappingStartAt -These settings are no longer used due to the full automation of UpdateRuleOwner. - -# 9.0.24 - 27.04.2026 DEVELOP -- introduce new modelling integration mode WorkflowNotifications - -# 9.1.0 - 20.05.2026 DEVELOP -- JWT refresh token -- introduce flow schema - -# 9.1.1 - 21.05.2026 DEVELOP -- Workflow: add configurable execution order for actions assigned to states. - -# 9.1.3 - 27.05.2026 DEVELOP -- add optional workflow flow merging for Flow DB creation - -# 9.1.4 - 03.06.2026 DEVELOP -- remove legacy ownerLdapGroupNames owner mapping fallback - -# 9.1.5 - 05.06.2026 DEVELOP -- remove old jwt token lifetime config values -- asynchronous initial JWT bootstrap in the UI -- subscription-aware reconnect logic after JWT refresh -- a separate GraphQL subscription client path -- improved cancellation and JWT-expiry handling -- a small cleanup of exception logging for subscription errors - -# 9.1.6 - 08.06.2026 DEVELOP -- remove obsolete database last_seen fields - -# 9.1.7 - 08.06.2026 DEVELOP -security patch - -This PR hardens FWO installation and security-sensitive workflows. It restricts app data import file/script paths, reduces installer secret exposure, tightens Hasura config permissions, improves LDAP/install test idempotency, removes the obsolete webhook role, and fixes related installer/test reliability issues. It also includes targeted documentation and version updates. - -- Import file/script handling now restricts app data sources to allowed .json and .py files under the customizing directory, rejecting unsafe paths and logging file hashes. -- Installer secret handling now uses no_log, avoids passing Hasura secrets on command lines, and prints secret file locations instead of secret values. -- LDAP installation now uses password files, tolerates existing entries, seeds missing test LDAP parents, and avoids premature middleware restarts. -- Hasura permissions now prevent scoped users from modifying global config rows while giving middleware-server dedicated global config access. -- Installer and integration tests now wait for required services, tolerate missing optional customizing scripts, and improve cleanup/reliability behavior. -- The obsolete webhook role and its docs, service files, templates, syslog, logrotate, and playbook wiring were removed. -- The app data import UI now selects allowed import stems instead of accepting arbitrary free-form paths. -- Product documentation and revision history were updated for this security-hardening release. -- The password-change REST endpoint now has an explicit [Authorize] requirement so anonymous callers are rejected before password-change logic runs. -- Importer and customizing-script HTTP calls now use connect/read timeouts so a stalled firewall or API endpoint can no longer hang an importer worker indefinitely. -- FortiOS (REST) VIP/destination-NAT objects are now normalized to their external IP, so policies referencing VIP objects resolve instead of failing the import or losing coverage. -- Tenant settings role handling now mirrors the backend per operation: the page stays viewable for admin/auditor/fw-admin, adding/deleting tenants and saving device visibility are admin-only (matching the REST and Hasura permissions), and editing existing tenants is allowed for admin/fw-admin. -- Remaining hardcoded strings on the scheduler monitoring page were moved into the localization texts. -- The shared confirm dialogs now raise DisplayChanged(false) after a successful action, so the parent's bound visibility state no longer remains stale. - -# 9.1.8 - 16.06.2026 DEVELOP -- flow db: access flows now include time objects and allow/deny flag in their functional definition -- flow sync: add hash consistency check -- this update resets the flow db! - -# 9.1.9 - 18.06.2026 DEVELOP -- request workflow: add locked tickets and request tasks for automatically created change requests -- further integration flow into workflow diff --git a/documentation/revision-history-main.md b/documentation/revision-history-main.md deleted file mode 100644 index 939b1a9285..0000000000 --- a/documentation/revision-history-main.md +++ /dev/null @@ -1,618 +0,0 @@ -# Firewall Orchestrator Revision History MAIN branch - -pre-5, a product called IT Security Organizer and was closed source. It was developed starting in 2005. -In 2020 we decided to re-launch a new - -## 5.0 - -### 5.0.0 -rename isoadmin --> uiuser - -### 5.0.1 -create table report_schedule - -### 5.0.2 -Add column changes_found to import_control table - signifying if an import produced any changes - -### 5.0.3 -adding config_user column to config table for user specific config settings - -### 5.0.4 -add report_owner_id column to report table (but do not allow for sharing of generated reports yet) - -### 5.0.5 - 13.12.2020 -adjust all relevant tables to allow for growth beyond integer (turning into BIGINT) - -### 5.0.6 - 18.12.2020 -adding tenant_id to ldap_connection table (optional) to allow dedicated ldap_connections per tenant - -### 5.0.7 - 22.12.2020 -removing stm_report_typ table and references, adding report_schedule.report_schedule_name. - -### 5.0.8 - 23.12.2020 -adding report_name and report_filetype to report table - -### 5.0.9 - 28.12.2020 -remove unique constraint from uiuser_username in uiuser - -### 5.1.01 - 30.12.2020 -drop old functions to enable re-creation with bigint - -### 5.1.02 - 30.12.2020 -drop default value for uiuser_language in uiuser - -### 5.1.03 - 31.12.2020 -adding some reporting columns - -### 5.1.04 - 01.01.2021 -adding default report_templates - -### 5.1.05 - 06.01.2021 -adding first compliance report template (any rules) - -### 5.1.06 - 10.01.2021 -adding report template format fk and permissions - -### 5.1.07 - 14.01.2021 -- adding https reverse proxy in front of middleware server -- removing column report.report_filetype which has been replaced with relation report_schedule_format and extra fields report_json, report_pdf, report_csv, report_html -- adding report_schedule_repetitions - -### 5.1.08 - 18.01.2021 -- removing report table columns which are not needed: - - start_import_id - - stop_import_id - - report_generation_time - -### 5.1.09 - 20.01.2021 -- report_template fixes - - default templates get report_template_owner id=0 instead of null - - fixing template edit/delete buttons - - changing report.report_pdf type from bytea to TEXT - -### 5.1.10 - 25.01.2021 -- add debug_level to table management - -### 5.1.11 - 27.01.2021 -- add rule_metadata table and fill it during import -- removing rule_order from import rule process - -### 5.1.12 - 29.01.2021 -- replacing rule_metadata.mgm_id with dev_id -- removing rule_order commpletely - -### 5.1.13 - 19.02.2021 -- add ldap_searchpath_for_groups, ldap_type and ldap_pattern_length to ldap_connection - -### 5.1.14 - 18.04.2021 -- replacing all secrets with randomly generated ones - an upgrade is not possible - needs an uninstall/re-install! - -### 5.1.15 - 23.04.2021 -- adding culture info to language - -### 5.1.16 - 10.05.2021 -- adding direct link tables rule_[svc|nwobj|user]_resolved to make report object export easier - -### 5.1.17 - 28.05.2021 -- adding parent rule reference for layer and domain rule functionality - -### 5.2.1 - 30.05.2021 -- removing - - php ui - - database components not needed for v5 (text_msg, get_text) - -### 5.2.2 - 30.05.2021 -- replace description texts of roles in ldap by codes for language translation - -### 5.2.3 - 02.06.2021 -- changing rule constraint from mgm_id to dev_id (for rulebases with identical global rules) -- debug improvements -- moving temp dir from /tmp to /var/fworch/tmp -- migrating get_config from text/dict mixed to cleaner dict-only approach - -### 5.2.4 - 04.06.2021 -- changing db column management.ssh_public_key to nullable -- adjusting api calls - -### 5.2.5 - 04.06.2021 -- new role recertifier -- adapt rule_metadata table for recertification prototype - -### 5.2.6 - 07.06.2021 -- report rule - object reference now time aware - -### 5.2.7 - 14.06.2021 -- add column rule_decert_date to rule_metadata - -### 5.2.8 - 18.06.2021 -- add column rule_recertification_comment to rule_metadata - -### 5.3.1 - 01.07.2021 -- first public open source version - -### 5.3.2 - 05.07.2021 -- some minor bufixes - -### 5.3.3 - 10.07.2021 -- add column ldap_name to ldap_connection -- add column ldap_connection_id to uiuser - -### 5.3.4 - 29.07.2021 -- moving to API hasura v2.0 - -### 5.4.1 - 10.09.2021 -- moving towards full API-based importer modules -- in preparation for coming import changes - -### 5.4.2 - 17.09.2021 -- as a start of FortiManager importer only some network objects are imported (PoC) -- renaming fortimanager version -- adding importer loop for new API based imports - -### 5.5.1 - 27.10.2021 -- preparing DB for NAT rules (transforming all existing rules) -- restricting all existing reports to access rules -- introducing swagger REST API for user management -- adding swagger REST API interactive documentation for user management -- moving to hasura 2.0.10 for slight performance boost (see https://github.com/hasura/graphql-engine/releases/tag/v2.0.10) - -### 5.5.2 - 06.11.2021 -- new default report template for NAT rules - -### 5.5.3 - 08.12.2021 -- add column global_tenant_name to ldap_connection - -### 5.5.4 - 13.12.2021 -- insert default config values - -### 5.5.5 - 20.12.2021 -- set ldap_tenant_level to 5 - -### 5.5.6 - 02.01.2022 -- introducing multi device manager for auto discovery - -### 5.5.7 - 04.01.2022 -- add column report_parameters to report_template, update data for default templates - -### 5.6.1 - 12.01.2022 -- update data for default templates for time filter - -### 5.6.2 - 17.01.2022 -- adding new legacy fortigate all in one device type (ssh) -- clean separation of legacy and api importer - -### 5.6.3 - 19.01.2022 -- migrating jsonb import config fields (import_config and import_full_config tables) to json -- this allows for import of bigger configs but is only a workaround that will not help for configs with >40.000 rules - -### 5.6.4 - 25.01.2022 -- main release merge -- migrated api import-loop to a single python script without any sys executes of ext. scripts -- minor but fixes and vip nat for fortimanager - -### 5.6.5 - 11.02.2022 -- next planned release -- fixing migration scripts -- splitting import_config into chunks to enable import of big managements -- introducing fw-admin role (device admin without delete & auto-discovery rights) -- working fortinet src hide nat behind interface - -### 5.6.6 - 07.03.2022 -- allow for users in rule destination (CP) -- monitoring module -- CPR8x auto-discovery - -### 5.6.7 - 04.04.2022 -- allow deactivation of ldap connection -- rework of python logging -- db index optimization -- fixing CIDR filtering - -### 5.6.8 -- no end ip address for obj types <> range -- fixing range display in reporting - -### 5.6.9 - 28.04.2022 -- import of fw configs directly from URL (import-mgm.py -m 17 -i https://x.y/z.conf) -- ldap connection check improvements -- alerting - handle import attempts - -### 5.7.1 - 13.10.2022 -- new workflow module for requesting changes -- new Cisco FirePower import module -- support for new operating system debian testing -- bugfix enrichable objects in CP NAT rules -- bugfix filter line brackets -- new central credentials handling for import - -### 5.7.2 - 21.10.2022 -- start routing/interface (implemented for fortinet only) import and path analysis -- also adding dummy router for testing and interconnecting routing clouds -- new report type: resolved rules (report without group objects, exporting into pure rule tables without additional object tables) -- new user management API call for creating JWTs with arbitrary lifetime - -### 5.8 - 23.10.2022 -- fix for CP R81 bug with certain installations - we now allow for domain UID as well as domain name for getting import data -- also adding domain UID in auto discovery module -- from now on reserving 3 digit version numbers for bug fixes only - -### 5.8.1 - 26.10.2022 -- hotfix DB user group import - -### 5.8.2 - 30.10.2022 -- new report type resolved tech info (no names) -- fix for log file rotation issues (log lock) -- fix change report warning for empty reports - -### 6.0 - 02.11.2022 -- clean-up work and new major version - -### 6.0.1 - 10.11.2022 -- bugfix release with small issues (userconfig re-login, ldif upgrade bug, debian testing support) - -### 6.0.2 - 24.12.2022 -- bugfix release with hasura API upgrade due to security bug in hasura - -### 6.2 - 16.03.2023 MAIN -- enhanced recertification module: adding ip-base recertification -- adding import modules for Palo Alto and Azure Firewall -- Workflow Module: adding delete rule request and integrated path analysis into workflow - -### 6.2.1 18.03.2023 MAIN -- fix ldap issues - closes ldap bugs #2023 -- reduced logging in release mode -- hasura v2.21.0 upgrade - -### 6.3 24.04.2023 MAIN -- adding CP R8X object types - - application categories - - updatable objects - - domain names - -### 6.3.1 27.04.2023 MAIN -- hotfix adding CP R8X object type application site - -### 6.3.2 05.05.2023 MAIN -- hotfix UI and fortigate importer credential handling -- checkpoint R8X importer adding support for Internet object type -- reporting - CSV export for change report - -### 6.4 25.05.2023 MAIN -- New importer module for importing FortiGate directly via FortiOS REST API -- Reporting: new lean export format JSON for resolved and tech reports -- hotfix FortiGate FortiOS REST importer: removing reference to gw_networking -- hotfix CPR8x importer: handling of empty section headers - -### 6.4.1 02.06.2023 MAIN -- FortiOS importer: add support for internet services - -### 6.4.2 05.06.2023 MAIN -- Hotfix - log locking UI hangs on prod systems due to infrequent log entries - -### 6.4.3 05.06.2023 MAIN -- Hotfix - global config subsription timout after 12h - -### 7.0 26.07.2023 MAIN -- new features - - UI adding compliance matrix module - - UI Reporting - unused rules report including delete ticket integration - - importer new email notification on security relevant import changes - - importer CPR8x: basic support for importing inline layers - -- maintenance / bug-fixing - - API: upgrading hasura api to 2.30.1 - - importer Fortigate API: hotfix NAT rules - - UI: cleanup around buttons and logout session handling - - UI Reporting: fixes links to objects, template name display, UI visibility for fw-admin role (multiple pages) - - UI (re-)login: allow enter as submit - - UI reporting: filter objects properly in rule report - - UI updating help pages: email & importer settings, archive, scheduling) - - installer: supress csharp test results on success - - demo data: fix sample group role path - - adding demo video in github README.MD - - splitting revision history into develop and main - -### 7.3 22.10.2023 MAIN -- new features - - recertification: new rule ownership - - customizable UI texts - - starting target state module with introducing new role "modeller" - - adding tenant ip filtering - - adding tenant simulation (exluding statistical report and recertification) including scheduling -- maintenance / bug-fixing - - complete re-work: all ip addresses are now internally represented as ranges, including all networks - - UI: - - do not show super managers in RSB all tab - - Use production / development based on the build type instead of always using development. - - do not show detailed errors in production mode + use the custom error page in the production environment - - bug fix jwt expiry, jwt expiry timer now works as intended - - unifying IP addresses display method across all parts - - fix filtering for rules with negated source / destination or single negated ip ranges - - Database: - - removing unused materialized view for tenant ip filtering - - Installer - - fix upgrade become issue in middleware ldif files - - fix client/server db sort order mismatch (collate) - - fix postgresql_query module reference - - adding simulated changes to fwodemodata (fortigate) - - add check for successful publishing dotnet (mw, ui) - - Importer - - fortiOS: fix importer action field - - fortimanager: ignore missing negate fields - - Check Point: adding Inform action - - Check Point: adding new network object type 'external-gateway' (for interoperable-dervice) - - Check Point: adding network object type support for 'CpmiVsClusterNetobj' (for VSX virtual switches) - - API: - - upgrade hasura to 2.34.0 -- restrictions - - since tenant filtering is not done in the API but in the UI, the API should not be exposed to the tenants - -### 8.0 19.02.2024 MAIN -- Introducing new Network Modelling module - - allows your organisation to define the target state of all network connection on a per-application basis (or other distributed ownerships) -- Backend - - Introducing Scheduled import change notification including inline or attached change report (replacing simple import notification from import module) - - upgrade hasura graphql API to 2.37.0 -- UI - - New look and feel: Moving to vanilla bootstrap css v5.3.2 (allowing for future up to date css usage) - - ip based tenant filtering: introducing unfiltered_managements and devices and adding extended tenant to device mapping settings -- Installer (breaking change!) - - introducing venv for newer ansible versions and thereby removing annoying ansible version handling in installer (see https://github.com/CactuseSecurity/firewall-orchestrator/blob/main/documentation/installer/basic-installation.md for details) -- bugfixes for - - import log locking - - integration tests with credentials when installing without demo data - - pdf creation on debian testing plattform (trixie) - -# 8.1 - 10.04.2024 MAIN -- UI: iconifying modelling UI buttons (can now use icons instead of text buttons - configurable per user) -- Importer: first version of VMware NSX import module -- API: adding customizing script for bulk configs via API -- Database security: all credentials in the database are now encrypted - breaking change (for developer debugging only): add the following local file when using -e testkeys=true: - /etc/fworch/secrets/main_key with content "not4production..not4production.." -- Importer fix: remove log locking from importer due to stalling importer stops - -# 8.2 - 30.04.2024 MAIN -- new workflow for modelling: interface request - - adding all imported modelling users to local db (uiuser) - to enable email notification -- new features for modelling - - display NAs in Report LSB and Export - - count and display members of areas in selection list -- upgrade to dotnet 8.0 (middleware and UI server) -- encrypt emailPassword in config -- fixes: - - demo managements (change import from deactivated to activated - does not affect test managements) - -# 8.3 - 25.06.2024 MAIN -Maintenance release -- fix misleading login error message when authorisation is missing -- fix email credential decryption -- start of Tufin SecureChange integration -- remove cascading delete for used interfaces -- owner-filtering for new report type -- new setting for email recipients -- owner-import custom script improvements# - -# 8.3.1 - 14.08.24 MAIN -Hotfix: -- in CheckPoint importer: fix missing group members - -# 8.4 - 30.09.24 MAIN -Stability release -- various small bug fixes - - installer (redundant code deleting test user) - - importer (switching from full details to standard, re-adding VSX gateway support, voip domain handling in cp parser) - - reporting (app-rule report containing multiple objects) - - middleware (config subscriptions) - - reporting (temporarily highlight linked to object in rsb) - - modelling (sync connections - not always part of overview table after creation) - - RBA (role picking when user has multiple roles) - - UI various: adding missing pager control - - UI various: spinner clean-up -- features/upgrades - - Added login page welcome message and settings - - Added last hit information in app-rule report - - API - upgrading to 2.43.0 - - various security upgrades dotnet (restsharp, jwt, ...) - -# 8.4.1 - 30.10.24 MAIN -Network Modelling feature update -- import of app server IP addresses via CSV upload -- import of multiple sources for area IP data -- new option email notification: fall-back to main owner if group is empty -Fixes -- corrections in displaying UI messages -- converting owner network ip data to standard format "range" -- importer - - check point - fix import of all VSX instances - - fortinet - add hit counts and install on information - -# 8.5 - 13.11.24 MAIN -Network Modelling feature update -- modelling can be requested as firewall change via external ticketing tool -- includes all approle handling -- simple form of rule change request (always request all connections as rules) -- api hasura upgrade to 2.44.0 -Fixes -- various small UI fixes -- importer (CP: handle None objects) - -# 8.6 - 11.12.2024 MAIN -Features -- Modelling - - Create Application Zones - - Add monitoring for external requests for admins - - Add re-initialization for external requests - - consolidation modelling external requests - - adding optional access requst on behalf of UI user - - adding live update of external task/ticket status - - app server name handling rework (NONAME --> _) - - owner groups can now also be external LDAP groups - -- Reporting - - refining connection report (adding Common service, app role, network area details) -Fixes -- Importer - - adding missing colors in Check Point importer - - new VOIP service object and Internet object - -- UI - - SECURITY: updating System.Text.Encodings.Web v4.5.0 --> v8.0.0 - -# 8.6.1 17.12.2024 MAIN -Fixes network modelling -- lock external requests to avoid multiple external tickets -- fix missing comments -- wait cycles for access request after group changes -- save publish flag at interface creation -- disregard dummyAppRole for status determination -- inherit extra configs from interface -- sanitize extra configs -- sort tasks for connection Id and show already adapted name of new members -- small monitoring adaptations -- some cleanup + removal of compiler warnings -- fix ldap group creation regression -- restrict owner_network uniqness constraint to same import source -- UI interface search pop-up transformed into filterable table - -Upgrade Hasura API to v2.45.1 - -# 8.6.2 03.01.2025 MAIN -Hotfix for network modelling: -- fix: when visiting the library for the second time, app servers were missing due to uninitialized area data. - - -# 8.7 03.03.2025 MAIN -- General UI - - pop-up unification and clean-up - - removing unnecessary scroll-bars -- PDF generation: replacing engine wkhtml with puppeteer -- Modelling - - Edit application role (AR): make objects sortable by IP or name - - adding change requests to history - - adding option to name all application servers by reverse DNS and fall-back to prefix + ip -- API: upgrade Hasura to 2.45.2 -- Workflow: some performance improvements - -# 8.7.1 07.03.2025 MAIN -- fix modelling select existing interfac -- fix modelling settings ldap selection -- fix workflow ticket close spinner - -# 8.8 17.04.2025 MAIN -* fix stm_action by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2844 -* add missing rulebase_link constraints by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2845 -* fix rule_metadata creation by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2865 -* remove dev_id fk constraint from rule_metadata by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2909 -* fix missing rule_metadata.rulebase_id by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2911 -* fix warnings and rule normalize bug by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2912 -* fix missing upgrade scripts from pre 9 by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2938 -* Cactus develop fix importer main level bug by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3009 -* Endpoint for getting rules by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3027 -* ExtRequest - increase logging by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3029 -* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3038 -* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3042 -* fix(ui): ip filtering in app report by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3040 -* Preventing use of NA objects in connections by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3043 -* fix(ui rsb): ui crash likely caused by duplicates in query result by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3046 -* LDAP Nuget Update changes by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3056 -* Defer AZ creation until second button click by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2856 -* Removing minor py-re deprecation warnings by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3053 -* feat(ui): rsb enhancements by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3073 -* User UI glitch by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3089 -* Modelling new AR drop down strange initial value by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3091 -* Verify modelled services for empty groups by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3087 -* adding app servers fails without name by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3088 -* Modelling - no NA should be usable for selected interfaces by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3086 -* new customized app data import script by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3101 -* adding csv appdata import stats by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3103 -* reformatting app server ip struct by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3105 -* css cache changes by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3108 -* show more clearly if everything is (horizontally) displayed by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3096 -* Fixed connection object duplication by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3118 -* Modelling csv import improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3113 -* IP check improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3133 -* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3136 -* Some report generation improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3117 -* Config change subscribe add "autoReplaceAppServer" #3138 by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3148 -* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3143 -* External ticket timout fix by @NilsPur in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3151 -* feat(ui): ip filter line observes negation in rules by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3164 -* allow for flexible ldap group name templating, fix #3114 by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3165 -* Variance Report First Throw by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3080 -* feat(ui rsb): show ip/port of flat members by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3172 -* fix(ui report): ip filter on negated rule to/from by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3173 - -# 8.8.1 - 28.04.2025 MAIN -- fix owner group DN for App Data Import module - -# 8.8.6 - 22.07.2025 MAIN -hotfix release -- CP importer new - - stm_track: "extended log" and "detailed log" - - fixing services-other ip proto import -- improved quality control with stricter automated checks -- various fixes in modelling module - -# 8.8.8 - 23.08.2025 MAIN -- add read-only db user fwo_ro -- hadening changes - - apache config (information leakage) - - listeners (hasura, postgres) - - log santisation -# 8.9.1 - 02.10.2025 MAIN -- owner-recertification - -# 8.9.2 - 17.10.2025 MAIN -- add ownerLifeCycleState -- add manageable ownerLifeCycleState menu - -# 8.9.3 - 05.11.2025 MAIN -- hotfix missing permissions for app data import in certain constellations - -## 8.9.4 - 09.12.2025 MAIN -- bugfix release: common service connection not editable -- new custom scripts for iiq and cmdb import - - -## 8.9.5 - 10.12.2025 MAIN -- bugfix release: modelling - change planning showed duplicate NA elements for rule delete requests - -## 8.9.6 - 05.01.2026 MAIN -- new parameters for notifications - -# 9.0 - 27.01.2026 MAIN -A complete 80K lines rework of FWO, including -- database changes to deduplicate rules (rule to gateway mapping now 1:n by introducing rulebase and rulebase_link tables) -- migrating import module from mixed python/pgsql to pure python - -# 9.0.1 - 07.02.2026 -- update rule_owner table for REST api -- update import_control to allow flexible tracking of different import types -- generalized owner responsibles with configurable responsible types -- add allow_write_access to responsible types to control modelling and recertification - -# 9.0.2 - 10.02.2026 DEVELOP -- fix: chunking for cleanup importer - -# 9.0.4 - 13.02.2026 MAIN -- maintenance release with explicit 9.0.4 upgrade step - -# 9.0.9 - 25.02.2026 DEVELOP -- remove stale v8 code - -# 9.0.16 - 26.03.2026 MAIN -- bug fixing -- moving from docker to podman - -# 9.0.21 - 21.04.2026 MAIN -- bug fixing -- dependency updates (notably closing mailkit and pytest vulnerabilities) - -# 9.0.22 - 26.04.2026 MAIN -- fixes missing source or destination in rule expiry notification report -- fixes time zone issues with checkpoint time objects -- fixes python tests failing on python 3.10 -- fixes owner import from custom file - -### 9.1.2 - 26.05.2026 -- database: fix flow foreign key duplication on fresh install plus upgrade path diff --git a/documentation/revision-history.md b/documentation/revision-history.md new file mode 100644 index 0000000000..e045d00077 --- /dev/null +++ b/documentation/revision-history.md @@ -0,0 +1,517 @@ +# Firewall Orchestrator Revision History + +pre-5, a product called IT Security Organizer and was closed source. It was developed starting in 2005. +In 2020 we decided to re-launch a new + +## 8.0 - 19.02.2024 MAIN +- Introducing new Network Modelling module + - allows your organisation to define the target state of all network connection on a per-application basis (or other distributed ownerships) +- Backend + - Introducing Scheduled import change notification including inline or attached change report (replacing simple import notification from import module) + - upgrade hasura graphql API to 2.37.0 +- UI + - New look and feel: Moving to vanilla bootstrap css v5.3.2 (allowing for future up to date css usage) + - ip based tenant filtering: introducing unfiltered_managements and devices and adding extended tenant to device mapping settings +- Installer (breaking change!) + - introducing venv for newer ansible versions and thereby removing annoying ansible version handling in installer (see https://github.com/CactuseSecurity/firewall-orchestrator/blob/main/documentation/installer/basic-installation.md for details) +- bugfixes for + - import log locking + - integration tests with credentials when installing without demo data + - pdf creation on debian testing plattform (trixie) + +## 8.0.1 - 20.02.2024 +- iconify modelling +- add missing config values + +## 8.0.2 - 11.03.2024 +- first version of NSX import module + +## 8.0.3 - 08.04.2024 +- add maintenance page during upgrade +- sample customizing py script with sample data, closes Installer customizable config (settings) #2275 +- remove log locking from importer due to stalling importer stops +- credentials encryption, closes encrypt passwords and keys #1508 + - breaking change for developer debugging: add the following local file when using -e testkeys=true: + /etc/fworch/secrets/main_key with content "not4production..not4production.." +- add custom (user-defined) fields to import + - cp only so far, other fw types missing + - user-defined fields are not part of reports yet + +## 8.1 - 10.04.2024 MAIN +- UI: iconifying modelling UI buttons (can now use icons instead of text buttons - configurable per user) +- Importer: first version of VMware NSX import module +- API: adding customizing script for bulk configs via API +- Database security: all credentials in the database are now encrypted - breaking change (for developer debugging only): add the following local file when using -e testkeys=true: + /etc/fworch/secrets/main_key with content "not4production..not4production.." +- Importer fix: remove log locking from importer due to stalling importer stops + +## 8.1.1 - 15.04.2024 +- interface request workflow first version + +## 8.1.2 - 22.04.2024 +- encrypt emailPassword in config +- fix demo managements (change import from deactivated to activated - does not affect test managements) +- upgrade to dotnet 8.0 +- adding all imported modelling users to uiuser + +## 8.2 - 30.04.2024 MAIN +- new workflow for modelling: interface request + - adding all imported modelling users to local db (uiuser) - to enable email notification +- new features for modelling + - display NAs in Report LSB and Export + - count and display members of areas in selection list +- upgrade to dotnet 8.0 (middleware and UI server) +- encrypt emailPassword in config +- fixes: + - demo managements (change import from deactivated to activated - does not affect test managements) + +## 8.2.1 - 03.05.2024 +- fix misleading login error message when authorisation is missing + +## 8.2.2 - 14.05.2024 +- fix email credential decryption +- start of Tufin SecureChange integration + +## 8.2.3 - 26.05.2024 +- remove cascading delete for used interfaces +- new properties field in connections + +## 8.2.4 - 19.06.2024 +- owner-filtering for new report type +- new setting for email recipients + +## 8.3 - 25.06.2024 MAIN +Maintenance release +- fix misleading login error message when authorisation is missing +- fix email credential decryption +- start of Tufin SecureChange integration +- remove cascading delete for used interfaces +- owner-filtering for new report type +- new setting for email recipients +- owner-import custom script improvements# + +## 8.3.1 - 08.07.2024 +- workflow: external state handling +- fix config value +- remove uniqueness of owner names + +## 8.3.1 - 14.08.24 MAIN +Hotfix: +- in CheckPoint importer: fix missing group members + +## 8.3.2 - 09.09.2024 +- Added welcome message and settings + +## 8.4 - 30.09.24 MAIN +Stability release +- various small bug fixes + - installer (redundant code deleting test user) + - importer (switching from full details to standard, re-adding VSX gateway support, voip domain handling in cp parser) + - reporting (app-rule report containing multiple objects) + - middleware (config subscriptions) + - reporting (temporarily highlight linked to object in rsb) + - modelling (sync connections - not always part of overview table after creation) + - RBA (role picking when user has multiple roles) + - UI various: adding missing pager control + - UI various: spinner clean-up +- features/upgrades + - Added login page welcome message and settings + - Added last hit information in app-rule report + - API - upgrading to 2.43.0 + - various security upgrades dotnet (restsharp, jwt, ...) + +## 8.4.1 - 15.10.24 +- Add missing FK connection.proposed_app_id #2591 + +## 8.4.2 - 17.10.2024 +- external request + +## 8.4.1 - 30.10.24 MAIN +Network Modelling feature update +- import of app server IP addresses via CSV upload +- import of multiple sources for area IP data +- new option email notification: fall-back to main owner if group is empty +Fixes +- corrections in displaying UI messages +- converting owner network ip data to standard format "range" +- importer + - check point - fix import of all VSX instances + - fortinet - add hit counts and install on information + +## 8.4.3 - 05.11.2024 +- extra parameters in modelling connection + +## 8.5 - 13.11.24 MAIN +Network Modelling feature update +- modelling can be requested as firewall change via external ticketing tool +- includes all approle handling +- simple form of rule change request (always request all connections as rules) +- api hasura upgrade to 2.44.0 +Fixes +- various small UI fixes +- importer (CP: handle None objects) + +## 8.5.1 - 18.11.2024 +- reporting - fixing PDF generation on various platforms +- modelling - fixing AR editing: strict prevention of all area mixing + +## 8.5.2 - 27.11.2024 +- some check point importer fixes + - 4 new colors + - added Internet object + - added voip one more object + +## 8.5.3 - 27.11.2024 +- owner import - make ldap selectable (internal/external) +- small fixes regarding missing config data for two schedulers (daily, app data import) + +## 8.5.4 - 04.12.2024 +- external request: introduce wait cycles + +## 8.6 - 11.12.2024 MAIN +Features +- Modelling + - Create Application Zones + - Add monitoring for external requests for admins + - Add re-initialization for external requests + - consolidation modelling external requests + - adding optional access requst on behalf of UI user + - adding live update of external task/ticket status + - app server name handling rework (NONAME --> _) + - owner groups can now also be external LDAP groups +- Reporting + - refining connection report (adding Common service, app role, network area details) +Fixes +- Importer + - adding missing colors in Check Point importer + - new VOIP service object and Internet object +- UI + - SECURITY: updating System.Text.Encodings.Web v4.5.0 --> v8.0.0 + +## 8.6.1 - 12.12.2024 +- external request: introduce locks + +## 8.6.1 - 17.12.2024 MAIN +Fixes network modelling +- lock external requests to avoid multiple external tickets +- fix missing comments +- wait cycles for access request after group changes +- save publish flag at interface creation +- disregard dummyAppRole for status determination +- inherit extra configs from interface +- sanitize extra configs +- sort tasks for connection Id and show already adapted name of new members +- small monitoring adaptations +- some cleanup + removal of compiler warnings +- fix ldap group creation regression +- restrict owner_network uniqness constraint to same import source +- UI interface search pop-up transformed into filterable table + +Upgrade Hasura API to v2.45.1 + +## 8.6.2 - 03.01.2025 MAIN +Hotfix for network modelling: +- fix: when visiting the library for the second time, app servers were missing due to uninitialized area data. + +## 8.6.3 - 20.02.2025 +- dns lookup for app server names + +## 8.7 - 03.03.2025 MAIN +- General UI + - pop-up unification and clean-up + - removing unnecessary scroll-bars +- PDF generation: replacing engine wkhtml with puppeteer +- Modelling + - Edit application role (AR): make objects sortable by IP or name + - adding change requests to history + - adding option to name all application servers by reverse DNS and fall-back to prefix + ip +- API: upgrade Hasura to 2.45.2 +- Workflow: some performance improvements + +## 8.7.1 - 05.03.2025 +- ldap writepath for groups + +## 8.7.1 - 07.03.2025 MAIN +- fix modelling select existing interfac +- fix modelling settings ldap selection +- fix workflow ticket close spinner + +## 8.7.2 - 20.03.2025 +- new config values +- external request: attempt counter + +## 8.8 - 17.04.2025 MAIN +* fix stm_action by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2844 +* add missing rulebase_link constraints by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2845 +* fix rule_metadata creation by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2865 +* remove dev_id fk constraint from rule_metadata by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2909 +* fix missing rule_metadata.rulebase_id by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2911 +* fix warnings and rule normalize bug by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2912 +* fix missing upgrade scripts from pre 9 by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2938 +* Cactus develop fix importer main level bug by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3009 +* Endpoint for getting rules by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3027 +* ExtRequest - increase logging by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3029 +* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3038 +* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3042 +* fix(ui): ip filtering in app report by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3040 +* Preventing use of NA objects in connections by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3043 +* fix(ui rsb): ui crash likely caused by duplicates in query result by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3046 +* LDAP Nuget Update changes by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3056 +* Defer AZ creation until second button click by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/2856 +* Removing minor py-re deprecation warnings by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3053 +* feat(ui): rsb enhancements by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3073 +* User UI glitch by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3089 +* Modelling new AR drop down strange initial value by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3091 +* Verify modelled services for empty groups by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3087 +* adding app servers fails without name by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3088 +* Modelling - no NA should be usable for selected interfaces by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3086 +* new customized app data import script by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3101 +* adding csv appdata import stats by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3103 +* reformatting app server ip struct by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3105 +* css cache changes by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3108 +* show more clearly if everything is (horizontally) displayed by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3096 +* Fixed connection object duplication by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3118 +* Modelling csv import improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3113 +* IP check improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3133 +* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3136 +* Some report generation improvements by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3117 +* Config change subscribe add "autoReplaceAppServer" #3138 by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3148 +* Nuget Updates by @SolidProgramming in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3143 +* External ticket timout fix by @NilsPur in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3151 +* feat(ui): ip filter line observes negation in rules by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3164 +* allow for flexible ldap group name templating, fix #3114 by @tpurschke in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3165 +* Variance Report First Throw by @abarz722 in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3080 +* feat(ui rsb): show ip/port of flat members by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3172 +* fix(ui report): ip filter on negated rule to/from by @Y4nnikH in https://github.com/CactuseSecurity/firewall-orchestrator/pull/3173 + +## 8.8.2 - 07.05.2025 +- displayed state via variance analysis + +## 8.8.3 - 15.05.2025 +- deactivation of connections + +## 8.8.4 - 02.06.2025 +- hotfix for Check Point importer suppor for DLP actions (ask, inform) + +## 8.8.5 - 17.06.2025 +- new enum values for Request Element Field Types +- hotfix change recognition: separate rule changes and "all changes" to make object version handling work properly + +## 8.8.6 - 08.07.2025 +- hotfix CP importer new stm_track: "extended log" and "detailed log" + +## 8.8.6 - 22.07.2025 MAIN +hotfix release +- CP importer new + - stm_track: "extended log" and "detailed log" + - fixing services-other ip proto import +- improved quality control with stricter automated checks +- various fixes in modelling module + +## 8.8.8 - 21.08.2025 +- add read-only db user fwo_ro +- also reducing db listener to localhost and other hardening changes + +## 8.8.8 - 23.08.2025 MAIN +- add read-only db user fwo_ro +- hadening changes + - apache config (information leakage) + - listeners (hasura, postgres) + - log santisation + +## 8.8.9 - 27.08.2025 +- prepare tables + settings for owner recert + first throw recert popup +- notification service +- decommissioning of interfaces +- iconification of modelling and related modules +- fix overwrite of objects with interface + +## 8.8.10 - 07.09.2025 +- new report type owner-recertification + +## 8.9.1 - 02.10.2025 MAIN +- owner-recertification + +## 8.9.2 - 17.10.2025 MAIN +- add ownerLifeCycleState +- add manageable ownerLifeCycleState menu +- fix two modelling ui glitches + +## 8.9.3 - 05.11.2025 MAIN +- hotfix missing permissions for app data import in certain constellations + +## 8.9.4 - 09.12.2025 MAIN +- bugfix release: common service connection not editable +- new custom scripts for iiq and cmdb import + +## 8.9.5 - 10.12.2025 MAIN +- bugfix release: modelling - change planning showed duplicate NA elements for rule delete requests + +## 8.9.6 - 05.01.2026 MAIN +- new parameters for notifications + +## 9.0 - 24.01.2026 MAIN +A complete 80K lines rework of FWO, including +- database changes to deduplicate rules (rule to gateway mapping now 1:n by introducing rulebase and rulebase_link tables) +- migrating import module from mixed python/pgsql to pure python + +## 9.0.1 - 07.02.2026 MAIN +- generalized owner responsibles with configurable responsible types +- add allow_write_access to responsible types to control modelling and recertification + +## 9.0.2 - 10.02.2026 +- importer: call api chunked where needed + +**Breaking changes** +- Due to introduction of venv for all imports, the following steps have to be taken to manually import a config: + +```shell + sudo -u fworch -i + cd importer + source importer-venv/bin/activate + python3 ./import_mgm.py -m xy -fs -d1 +``` + As we now need support for pip, in installations behind url filter, make sure that all sub-domains of "pythonhosted.org" are also allowed. + +- Limiting database listener to localhost for security reasons + +## 9.0.3 - 12.02.2026 +- introduce interface permissions + +## 9.0.4 - 13.02.2026 MAIN +- maintenance release with explicit 9.0.4 upgrade step + +## 9.0.5 - 18.02.2026 +- update rule_owner table for REST api +- update import_control to allow flexible tracking of different import types +- create rule_owner mapping for custom_field via button and service/job +- update import_control to allow flexible tracking of different import types + +## 9.0.6 - 20.02.2026 +- add import of time objects + +## 9.0.7 - 25.02.2026 +- add import of time objects +- create changelog_owner table + +## 9.0.8 - 25.02.2026 +- new config value for removed App Server handling + +## 9.0.9 - 25.02.2026 +- remove stale v8 code + +## 9.0.10 - 28.02.2026 +- new config value for User synchronization in owner data import + +## 9.0.11 - 04.03.2026 +- new config value for requesting only own objects + +## 9.0.12 - 12.03.2026 +- new config values for rule expiry notification + +## 9.0.13 - 12.03.2026 +- mark lifecycle states as active + +## 9.0.14 - 17.03.2026 +- prepare owner decommission notification + +## 9.0.15 - 19.03.2026 +- rename OwnerSourceCustomFieldKey to CustomFieldOwnerKey in config + +## 9.0.16 - 26.03.2026 MAIN +- bug fixing +- moving from docker to podman + +## 9.0.16 - 31.03.2026 +- remove not needed stm_owner_mapping_source +- add Full re-initialize of RuleOwner mapping for IP-based rules +- add matched_objects field in rule_owner table for track matched objects - IpBased + +## 9.0.18 - 03.04.2026 +- add new column automatic_only to workflow states + +## 9.0.19 - 09.04.2026 +- add owner additional_info jsonb field including owner edit UI support + +## 9.0.20 - 11.04.2026 +- extend notification handling + +## 9.0.21 - 21.04.2026 MAIN +- fix ldap users with special chars not being processed correctly in role handling +- fix empty mail being sent for orphaned rule report +- update dependencies (notably closing mailkit and pytest vuln) +- fix time zone issues in importer + +## 9.0.22 - 26.04.2026 MAIN +- fixes missing source or destination in rule expiry notification report +- fixes time zone issues with checkpoint time objects +- fixes python tests failing on python 3.10 +- fixes owner import from custom file + +## 9.0.23 - 27.04.2026 +- enhance notifications by bcc +- add display-only workflow label report column option +- add default template for workflow tickets approved last week +Removed deprecated configuration keys: +- updateRuleOwnerMappingActive +- updateRuleOwnerMappingStartAt +These settings are no longer used due to the full automation of UpdateRuleOwner. + +## 9.0.24 - 27.04.2026 +- introduce new modelling integration mode WorkflowNotifications + +## 9.1.0 - 20.05.2026 +- JWT refresh token +- introduce flow schema + +## 9.1.1 - 21.05.2026 +- Workflow: add configurable execution order for actions assigned to states. + +## 9.1.2 - 26.05.2026 MAIN +- database: fix flow foreign key duplication on fresh install plus upgrade path + +## 9.1.3 - 27.05.2026 +- add optional workflow flow merging for Flow DB creation + +## 9.1.4 - 03.06.2026 +- remove legacy ownerLdapGroupNames owner mapping fallback + +## 9.1.5 - 05.06.2026 +- remove old jwt token lifetime config values +- asynchronous initial JWT bootstrap in the UI +- subscription-aware reconnect logic after JWT refresh +- a separate GraphQL subscription client path +- improved cancellation and JWT-expiry handling +- a small cleanup of exception logging for subscription errors + +## 9.1.6 - 08.06.2026 +- remove obsolete database last_seen fields + +## 9.1.7 - 08.06.2026 +security patch + +This PR hardens FWO installation and security-sensitive workflows. It restricts app data import file/script paths, reduces installer secret exposure, tightens Hasura config permissions, improves LDAP/install test idempotency, removes the obsolete webhook role, and fixes related installer/test reliability issues. It also includes targeted documentation and version updates. + +- Import file/script handling now restricts app data sources to allowed .json and .py files under the customizing directory, rejecting unsafe paths and logging file hashes. +- Installer secret handling now uses no_log, avoids passing Hasura secrets on command lines, and prints secret file locations instead of secret values. +- LDAP installation now uses password files, tolerates existing entries, seeds missing test LDAP parents, and avoids premature middleware restarts. +- Hasura permissions now prevent scoped users from modifying global config rows while giving middleware-server dedicated global config access. +- Installer and integration tests now wait for required services, tolerate missing optional customizing scripts, and improve cleanup/reliability behavior. +- The obsolete webhook role and its docs, service files, templates, syslog, logrotate, and playbook wiring were removed. +- The app data import UI now selects allowed import stems instead of accepting arbitrary free-form paths. +- Product documentation and revision history were updated for this security-hardening release. +- The password-change REST endpoint now has an explicit [Authorize] requirement so anonymous callers are rejected before password-change logic runs. +- Importer and customizing-script HTTP calls now use connect/read timeouts so a stalled firewall or API endpoint can no longer hang an importer worker indefinitely. +- FortiOS (REST) VIP/destination-NAT objects are now normalized to their external IP, so policies referencing VIP objects resolve instead of failing the import or losing coverage. +- Tenant settings role handling now mirrors the backend per operation: the page stays viewable for admin/auditor/fw-admin, adding/deleting tenants and saving device visibility are admin-only (matching the REST and Hasura permissions), and editing existing tenants is allowed for admin/fw-admin. +- Remaining hardcoded strings on the scheduler monitoring page were moved into the localization texts. +- The shared confirm dialogs now raise DisplayChanged(false) after a successful action, so the parent's bound visibility state no longer remains stale. + +## 9.1.8 - 16.06.2026 +- flow db: access flows now include time objects and allow/deny flag in their functional definition +- flow sync: add hash consistency check +- this update resets the flow db! + +## 9.1.9 - 18.06.2026 +- request workflow: add locked tickets and request tasks for automatically created change requests +- further integration flow into workflow diff --git a/documentation/version-feature-overview.md b/documentation/version-feature-overview.md index 2f18f3a419..c025193e34 100644 --- a/documentation/version-feature-overview.md +++ b/documentation/version-feature-overview.md @@ -1,8 +1,8 @@ # Firewall Orchestrator – Version / Feature Overview This document lists each Firewall Orchestrator version from **8.0** onwards and -the features it introduced. It is compiled from the revision histories -([main](revision-history-main.md), [develop](revision-history-develop.md)). +the features it introduced. It is compiled from the +[revision history](revision-history.md). Entries focus on newly introduced features and capabilities; pure bug fixes, dependency bumps and Hasura/.NET maintenance upgrades are generally omitted. A From 20045db2f97045bfd51b3ee0188cf9fcf437d94e Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Mon, 22 Jun 2026 09:28:33 +0200 Subject: [PATCH 10/14] force tst install --- roles/lib/files/FWO.Report/ReportOwners.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/roles/lib/files/FWO.Report/ReportOwners.cs b/roles/lib/files/FWO.Report/ReportOwners.cs index 15abc0241b..5d22229a9f 100644 --- a/roles/lib/files/FWO.Report/ReportOwners.cs +++ b/roles/lib/files/FWO.Report/ReportOwners.cs @@ -150,7 +150,6 @@ private string GetOwnerStateName(FwoOwner owner) { return owner.OwnerLifeCycleState.Name; } - return GetOwnerState(owner); } From c3c1f16aa8b691ed1b13fc7b758748f51a0d5adc Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Mon, 22 Jun 2026 09:38:37 +0200 Subject: [PATCH 11/14] remove importer-rework ref --- .github/workflows/test-install.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml index f6b22bf4ed..cc7558423e 100644 --- a/.github/workflows/test-install.yml +++ b/.github/workflows/test-install.yml @@ -5,7 +5,6 @@ on: branches: - main - develop - - importer-rework pull_request: From 455c386b38f7c195596d984d778686f1215c5079 Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Mon, 22 Jun 2026 12:42:26 +0200 Subject: [PATCH 12/14] incorporate review suggestions --- .../Controllers/OwnersController.cs | 64 ++++++++++++++++++- .../Requests/GetOwnersRequest.cs | 3 + .../files/FWO.Test/OwnersControllerTest.cs | 56 ++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs index dcb15a8ea2..85fde888f5 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Controllers/OwnersController.cs @@ -22,6 +22,7 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase { private const string StandardOwnerType = "standard"; private const string InfrastructureOwnerType = "infrastructure"; + private const int kMaxFilterTextLength = 256; /// /// Returns all owners visible to the caller with optional AND-combined filters. @@ -61,11 +62,15 @@ public class OwnersController(ApiConnection apiConnection) : ControllerBase /// By default owners with an inactive lifecycle state are excluded; set showOnlyActiveState to /// false to also include them. Owners without any lifecycle state are always returned. /// The name and appIdExternal filters are case-insensitive and accept * for any - /// character sequence and ? for a single character. Plain text without wildcards is matched as a contains search. + /// character sequence and ? for a single character. Plain text without wildcards is matched as a contains + /// search, and literal %, _, and \ characters are matched verbatim. + /// Unknown request properties are rejected with 400 Bad Request, as are non-positive ids and text + /// filters that exceed 256 characters or contain control characters. /// [HttpPost("get")] [Consumes("application/json")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] @@ -75,6 +80,11 @@ public async Task>> Get([FromBody] GetOwners try { GetOwnersRequest effectiveRequest = request ?? new GetOwnersRequest(); + if (ValidateRequest(effectiveRequest) is string validationError) + { + return BadRequest(validationError); + } + List owners = await apiConnection.SendQueryAsync>( OwnerQueries.getOwnersFiltered, BuildQueryVariables(effectiveRequest, User)) ?? []; @@ -88,6 +98,44 @@ public async Task>> Get([FromBody] GetOwners } } + /// + /// Validates and sanitizes the supplied filter values before they are used to build the query. + /// + /// The request to validate. + /// An error message describing the first invalid value, or null when the request is valid. + internal static string? ValidateRequest(GetOwnersRequest request) + { + if (request.OwnerId is <= 0) + { + return "ownerId must be a positive integer."; + } + if (request.OwnerLifeCycleStateId is <= 0) + { + return "ownerLifecycleStateId must be a positive integer."; + } + return ValidateFilterText(request.Name, "name") ?? ValidateFilterText(request.AppIdExternal, "appIdExternal"); + } + + /// + /// Ensures a text filter stays within the allowed length and contains no control characters. + /// + private static string? ValidateFilterText(string? value, string fieldName) + { + if (value is null) + { + return null; + } + if (value.Length > kMaxFilterTextLength) + { + return $"{fieldName} must not exceed {kMaxFilterTextLength} characters."; + } + if (value.Any(char.IsControl)) + { + return $"{fieldName} must not contain control characters."; + } + return null; + } + /// /// Builds GraphQL variables for the owner lookup. /// @@ -226,11 +274,21 @@ private static bool IsStandardOwner(string? appIdExternal) return appIdExternal?.Contains("app", StringComparison.OrdinalIgnoreCase) == true; } + /// + /// Builds an _ilike pattern from a user-supplied filter value. + /// Literal SQL wildcards (\, %, _) in the input are escaped so they are matched verbatim, + /// while the documented * and ? wildcards are translated to % and _. + /// Plain text without */? is wrapped for a contains search. + /// private static string BuildLikePattern(string value) { string trimmedValue = value.Trim(); - string pattern = trimmedValue.Replace('*', '%').Replace('?', '_'); - bool hasWildcard = pattern.Contains('%') || pattern.Contains('_'); + bool hasWildcard = trimmedValue.Contains('*') || trimmedValue.Contains('?'); + string escapedValue = trimmedValue + .Replace("\\", "\\\\") + .Replace("%", "\\%") + .Replace("_", "\\_"); + string pattern = escapedValue.Replace('*', '%').Replace('?', '_'); return hasWildcard ? pattern : $"%{pattern}%"; } } diff --git a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs index 63236daa17..b06e980faf 100644 --- a/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs +++ b/roles/middleware/files/FWO.Middleware.Server/Requests/GetOwnersRequest.cs @@ -4,7 +4,10 @@ namespace FWO.Middleware.Server.Requests; /// /// Represents owner lookup filters for the owners/get endpoint. +/// Unknown properties are rejected so callers are notified of typos or unsupported filters +/// instead of having them silently ignored. /// +[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)] public sealed class GetOwnersRequest { /// diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 6dd32bbab7..292f558651 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -107,6 +107,62 @@ await controller.Get(new GetOwnersRequest Assert.That(variables, Does.Contain("\"app_id_external\":{\"_ilike\":\"APP-_\"}")); } + [Test] + public async Task GetEscapesLiteralSqlWildcardsInFilters() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + await controller.Get(new GetOwnersRequest + { + Name = "APP_1", + AppIdExternal = "50%" + }); + + string variables = SerializeVariables(apiConnection.Variables); + Assert.Multiple(() => + { + Assert.That(variables, Does.Contain($"\"name\":{{\"_ilike\":{JsonSerializer.Serialize("%APP\\_1%")}}}")); + Assert.That(variables, Does.Contain($"\"app_id_external\":{{\"_ilike\":{JsonSerializer.Serialize("%50\\%%")}}}")); + }); + } + + [Test] + public async Task GetReturnsBadRequestForNonPositiveOwnerId() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(new GetOwnersRequest { OwnerId = 0 }); + + Assert.That(result.Result, Is.InstanceOf()); + Assert.That(apiConnection.Query, Is.Empty); + } + + [Test] + public async Task GetReturnsBadRequestForControlCharacterInName() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(new GetOwnersRequest { Name = "badname" }); + + Assert.That(result.Result, Is.InstanceOf()); + Assert.That(apiConnection.Query, Is.Empty); + } + + [Test] + public async Task GetReturnsBadRequestForOverlongFilter() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(new GetOwnersRequest { AppIdExternal = new string('a', 257) }); + + Assert.That(result.Result, Is.InstanceOf()); + Assert.That(apiConnection.Query, Is.Empty); + } + [Test] public async Task GetRestrictsModellerToEditableOwnerIds() { From 3abd6b4d4270ee5891c88ef473171617738aaadc Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Tue, 23 Jun 2026 13:01:10 +0200 Subject: [PATCH 13/14] trigger sonar --- roles/database/files/upgrade/9.1.10.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/database/files/upgrade/9.1.10.sql b/roles/database/files/upgrade/9.1.10.sql index b09abbbc68..a0d49988c8 100644 --- a/roles/database/files/upgrade/9.1.10.sql +++ b/roles/database/files/upgrade/9.1.10.sql @@ -1,5 +1,6 @@ -- The following changes are related to the addition of rule_src/dst_zone (text, containing joined zone names) to the rule table. -- Keeping the old columns rule_from/to_zone (int, currently unused) for now to avoid having to change existing views, some of which are not even used anymore +-- trigger sonar (ignore) ALTER TABLE IF EXISTS public.rule ADD COLUMN IF NOT EXISTS rule_src_zone TEXT; From 45fe8027f4be5057f0afe64caa2358815235692f Mon Sep 17 00:00:00 2001 From: Tim Purschke Date: Wed, 24 Jun 2026 17:45:04 +0200 Subject: [PATCH 14/14] test: cover owners controller --- .../files/FWO.Test/OwnersControllerTest.cs | 88 ++++++++++++++++++- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs index 292f558651..da6496a4dc 100644 --- a/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs +++ b/roles/tests-unit/files/FWO.Test/OwnersControllerTest.cs @@ -18,6 +18,11 @@ namespace FWO.Test [TestFixture] internal class OwnersControllerTest { + private static readonly string[] kOwnerControllerRoutes = ["api/owners"]; + private static readonly string[] kModellerRole = [Roles.Modeller]; + private static readonly string[] kAdminAndModellerRoles = [Roles.Admin, Roles.Modeller]; + private static readonly string[] kOwnerResponseTypes = ["standard", "infrastructure", "infrastructure"]; + [Test] public void GetUsesApiOwnersRoute() { @@ -25,7 +30,7 @@ public void GetUsesApiOwnersRoute() MethodInfo getMethod = typeof(OwnersController).GetMethod(nameof(OwnersController.Get))!; HttpPostAttribute? httpPost = getMethod.GetCustomAttribute(); - Assert.That(controllerRoutes.Select(route => route.Template), Is.EquivalentTo(new[] { "api/owners" })); + Assert.That(controllerRoutes.Select(route => route.Template), Is.EquivalentTo(kOwnerControllerRoutes)); Assert.That(httpPost?.Template, Is.EqualTo("get")); } @@ -139,6 +144,18 @@ public async Task GetReturnsBadRequestForNonPositiveOwnerId() Assert.That(apiConnection.Query, Is.Empty); } + [Test] + public async Task GetReturnsBadRequestForNonPositiveLifecycleStateId() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(new GetOwnersRequest { OwnerLifeCycleStateId = 0 }); + + Assert.That(result.Result, Is.InstanceOf()); + Assert.That(apiConnection.Query, Is.Empty); + } + [Test] public async Task GetReturnsBadRequestForControlCharacterInName() { @@ -163,13 +180,21 @@ public async Task GetReturnsBadRequestForOverlongFilter() Assert.That(apiConnection.Query, Is.Empty); } + [Test] + public void GetOwnersRequestRejectsUnknownJsonProperties() + { + string json = """{"ownerId":1,"unsupported":true}"""; + + Assert.Throws(() => JsonSerializer.Deserialize(json)); + } + [Test] public async Task GetRestrictsModellerToEditableOwnerIds() { OwnersApiConnection apiConnection = new(); OwnersController controller = CreateController( apiConnection, - PrincipalWithRolesAndClaims([Roles.Modeller], new Claim("x-hasura-editable-owners", "{7,8}"))); + PrincipalWithRolesAndClaims(kModellerRole, new Claim("x-hasura-editable-owners", "{7,8}"))); await controller.Get(new GetOwnersRequest()); @@ -183,7 +208,7 @@ public async Task GetDoesNotRestrictAdminWhoAlsoHasModellerRole() OwnersApiConnection apiConnection = new(); OwnersController controller = CreateController( apiConnection, - PrincipalWithRolesAndClaims([Roles.Admin, Roles.Modeller], new Claim("x-hasura-editable-owners", "{7,8}"))); + PrincipalWithRolesAndClaims(kAdminAndModellerRoles, new Claim("x-hasura-editable-owners", "{7,8}"))); await controller.Get(new GetOwnersRequest()); @@ -209,7 +234,7 @@ public async Task GetMapsOwnerResponseType() OkObjectResult okResult = (OkObjectResult)result.Result!; List owners = (List)okResult.Value!; - Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(new[] { "standard", "infrastructure", "infrastructure" })); + Assert.That(owners.Select(owner => owner.Type), Is.EqualTo(kOwnerResponseTypes)); } [Test] @@ -319,6 +344,50 @@ public async Task GetReturnsDetailFieldsWhenRequested() }); } + [Test] + public async Task GetTreatsNullRequestAsEmptyRequest() + { + OwnersApiConnection apiConnection = new(); + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(null); + + Assert.Multiple(() => + { + Assert.That(result.Result, Is.InstanceOf()); + Assert.That(SerializeVariables(apiConnection.Variables), Does.Contain("active_state")); + }); + } + + [Test] + public async Task GetReturnsEmptyListWhenApiReturnsNull() + { + OwnersApiConnection apiConnection = new() { ReturnNullOwners = true }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Auditor)); + + ActionResult> result = await controller.Get(new GetOwnersRequest()); + + OkObjectResult okResult = (OkObjectResult)result.Result!; + List owners = (List)okResult.Value!; + Assert.That(owners, Is.Empty); + } + + [Test] + public async Task GetReturnsInternalServerErrorWhenApiThrows() + { + OwnersApiConnection apiConnection = new() { ThrowOnQuery = true }; + OwnersController controller = CreateController(apiConnection, PrincipalWithRoles(Roles.Admin)); + + ActionResult> result = await controller.Get(new GetOwnersRequest()); + + ObjectResult objectResult = (ObjectResult)result.Result!; + Assert.Multiple(() => + { + Assert.That(objectResult.StatusCode, Is.EqualTo(StatusCodes.Status500InternalServerError)); + Assert.That(objectResult.Value, Is.EqualTo("Internal server error")); + }); + } + private static OwnersController CreateController(ApiConnection apiConnection, ClaimsPrincipal user) { return new OwnersController(apiConnection) @@ -352,6 +421,8 @@ private sealed class OwnersApiConnection : SimulatedApiConnection public string Query { get; private set; } = string.Empty; public object? Variables { get; private set; } public List Owners { get; set; } = []; + public bool ReturnNullOwners { get; set; } + public bool ThrowOnQuery { get; set; } public override Task SendQueryAsync( string query, @@ -359,8 +430,17 @@ public override Task SendQueryAsync( string? operationName = null, QueryChunkingOptions? chunkingOptions = null) { + if (ThrowOnQuery) + { + throw new InvalidOperationException("owner lookup failed"); + } + Query = query; Variables = variables; + if (ReturnNullOwners) + { + return Task.FromResult(default(QueryResponseType)!); + } return Task.FromResult((QueryResponseType)(object)Owners); } }