trail, int index)
{
if (index == 0
- && (!(link.LinkType == 4)
+ && (link.LinkType != 4 && link.LinkType != 6
|| lastAddedItem.Parent?.Children.Count() == 1))
{
trail = trail.ToList();
@@ -394,7 +394,7 @@ private void SetParentForTreeItem(RuleTreeItem item, RulebaseLink link, RuleTree
{
SetParentForTreeItem(RuleTree, item);
}
- else if (link.LinkType == 4 && lastAddedItem.Parent != null)
+ else if ((link.LinkType == 4 || link.LinkType == 6) && lastAddedItem.Parent != null)
{
SetParentForTreeItem((RuleTreeItem)lastAddedItem.Parent, item);
}
diff --git a/roles/tests-unit/files/FWO.Test/ExportTest.cs b/roles/tests-unit/files/FWO.Test/ExportTest.cs
index 774d159ef7..7b0de2aa92 100644
--- a/roles/tests-unit/files/FWO.Test/ExportTest.cs
+++ b/roles/tests-unit/files/FWO.Test/ExportTest.cs
@@ -872,7 +872,7 @@ public void RulesGenerateJson()
"\"rule_action\": \"accept\",\"rule_track\": \"none\",\"section_header\": \"\"," +
"\"rule_metadatum\": {\"rule_metadata_id\": 0,\"rule_created\": null,\"created_import\": null,\"removed\": null,\"removed_import\": null,\"rule_first_hit\": null,\"rule_last_hit\": \"2022-04-19T00:00:00Z\",\"recertification\": [],\"recert_history\": [],\"rule_uid\": \"\",\"rules\": [],\"Recert\": false}," +
"\"translate\": {\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_services\": [],\"rule_src_neg\": false,\"rule_src\": \"\",\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"rule_tos\": []}," +
- "\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"rule_custom_fields\": \"\",\"rule_implied\": false,\"nat_rule\": false,\"rulebase_id\": 0,\"rule_num\": 0,\"rule_enforced_on_gateways\": [],\"rule_installon\": null,\"rule_time\": null,\"rule_times\": [],\"violations\": [],\"rulebase\": {\"id\": 0,\"name\": \"\",\"uid\": \"\",\"mgm_id\": 0,\"is_global\": false,\"created\": 0,\"removed\": 0,\"rules\": []},\"uiuser\": null,\"rule\": null,\"rule_owners\": [],\"flow_access_id\": null,\"flow_access\": null,\"removed\": null,\"ChangeID\": \"\",\"AdoITID\": \"\",\"Compliance\": 0,\"ViolationDetails\": \"\",\"DisplayOrderNumberString\": \"1\",\"DisplayOrderNumber\": 1,\"Certified\": false,\"DeviceName\": \"\",\"RulebaseName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}," +
+ "\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"rule_custom_fields\": \"\",\"rule_implied\": false,\"nat_rule\": false,\"access_rule\": true,\"rulebase_id\": 0,\"rule_num\": 0,\"rule_enforced_on_gateways\": [],\"rule_installon\": null,\"rule_time\": null,\"rule_times\": [],\"violations\": [],\"rulebase\": {\"id\": 0,\"name\": \"\",\"uid\": \"\",\"mgm_id\": 0,\"is_global\": false,\"created\": 0,\"removed\": 0,\"rules\": []},\"uiuser\": null,\"rule\": null,\"ruleByXlateRule\": null,\"rule_owners\": [],\"xlate_rule\": null,\"flow_access_id\": null,\"flow_access\": null,\"removed\": null,\"ChangeID\": \"\",\"AdoITID\": \"\",\"Compliance\": 0,\"ViolationDetails\": \"\",\"DisplayOrderNumberString\": \"1\",\"DisplayOrderNumber\": 1,\"Certified\": false,\"DeviceName\": \"\",\"RulebaseName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}," +
"{\"rule_id\": 0,\"rule_uid\": \"uid2:123\",\"mgm_id\": 0,\"rule_num_numeric\": 0,\"rule_name\": \"TestRule2\",\"rule_comment\": \"comment2\",\"rule_disabled\": false," +
"\"rule_services\": [{\"service\": {\"svc_id\": 2,\"svc_name\": \"TestService2\",\"svc_uid\": \"019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"svc_port\": 6666,\"svc_port_end\": 7777,\"svc_source_port\": null,\"svc_source_port_end\": null,\"svc_code\": \"\",\"svc_timeout\": null,\"svc_typ_id\": null,\"active\": false,\"svc_create\": 0,\"svc_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"service_type\": {\"name\": \"\"},\"svc_comment\": \"Comment-019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"svc_color_id\": null,\"stm_color\": null,\"ip_proto_id\": null,\"protocol_name\": {\"id\": 17,\"name\": \"UDP\"},\"svc_member_names\": \"Member-019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"svc_member_refs\": \"\",\"svcgrps\": [],\"svcgrp_flats\": [],\"svc_rpcnr\": null,\"flow_svcobj_id\": null,\"flow_svcobject\": null,\"flow_svcgrp_id\": null,\"flow_svcgroup\": null,\"flow_active\": false,\"removed\": null}}]," +
"\"rule_svc_neg\": true,\"rule_svc\": \"\",\"rule_svc_refs\": \"\",\"rule_src_neg\": true,\"rule_src\": \"\",\"rule_src_refs\": \"\",\"rule_src_zone\": \"\",\"rule_from_zones\": []," +
@@ -885,7 +885,7 @@ public void RulesGenerateJson()
"\"rule_action\": \"deny\",\"rule_track\": \"none\",\"section_header\": \"\"," +
"\"rule_metadatum\": {\"rule_metadata_id\": 0,\"rule_created\": null,\"created_import\": null,\"removed\": null,\"removed_import\": null,\"rule_first_hit\": null,\"rule_last_hit\": null,\"recertification\": [],\"recert_history\": [],\"rule_uid\": \"\",\"rules\": [],\"Recert\": false}," +
"\"translate\": {\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_services\": [],\"rule_src_neg\": false,\"rule_src\": \"\",\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"rule_tos\": []}," +
- "\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"rule_custom_fields\": \"\",\"rule_implied\": false,\"nat_rule\": false,\"rulebase_id\": 0,\"rule_num\": 0,\"rule_enforced_on_gateways\": [],\"rule_installon\": null,\"rule_time\": null,\"rule_times\": [],\"violations\": [],\"rulebase\": {\"id\": 0,\"name\": \"\",\"uid\": \"\",\"mgm_id\": 0,\"is_global\": false,\"created\": 0,\"removed\": 0,\"rules\": []},\"uiuser\": null,\"rule\": null,\"rule_owners\": [],\"flow_access_id\": null,\"flow_access\": null,\"removed\": null,\"ChangeID\": \"\",\"AdoITID\": \"\",\"Compliance\": 0,\"ViolationDetails\": \"\",\"DisplayOrderNumberString\": \"\",\"DisplayOrderNumber\": 2,\"Certified\": false,\"DeviceName\": \"\",\"RulebaseName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}]}]," +
+ "\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"rule_custom_fields\": \"\",\"rule_implied\": false,\"nat_rule\": false,\"access_rule\": true,\"rulebase_id\": 0,\"rule_num\": 0,\"rule_enforced_on_gateways\": [],\"rule_installon\": null,\"rule_time\": null,\"rule_times\": [],\"violations\": [],\"rulebase\": {\"id\": 0,\"name\": \"\",\"uid\": \"\",\"mgm_id\": 0,\"is_global\": false,\"created\": 0,\"removed\": 0,\"rules\": []},\"uiuser\": null,\"rule\": null,\"ruleByXlateRule\": null,\"rule_owners\": [],\"xlate_rule\": null,\"flow_access_id\": null,\"flow_access\": null,\"removed\": null,\"ChangeID\": \"\",\"AdoITID\": \"\",\"Compliance\": 0,\"ViolationDetails\": \"\",\"DisplayOrderNumberString\": \"\",\"DisplayOrderNumber\": 2,\"Certified\": false,\"DeviceName\": \"\",\"RulebaseName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}]}]," +
"\"changelog_rules\": null,\"changelog_objects\": null,\"changelog_services\": null,\"changelog_users\": null,\"import\": {\"aggregate\": {\"max\": {\"id\": null}}},\"import_controls\": [],\"RelevantImportId\": null,\"is_super_manager\": false,\"multi_device_manager_id\": null,\"management\": null,\"managementByMultiDeviceManagerId\": [],\"networkObjects\": [],\"serviceObjects\": [],\"userObjects\": [],\"zoneObjects\": []," +
"\"reportNetworkObjects\": [{\"obj_id\": 1,\"obj_name\": \"TestIp1\",\"obj_ip\": \"1.2.3.4/32\",\"obj_ip_end\": \"1.2.3.4/32\",\"obj_uid\": \"019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"zone\": null,\"active\": false,\"obj_create\": 0," +
"\"obj_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"type\": {\"id\": 0,\"name\": \"network\"},\"obj_color\": null,\"obj_comment\": \"Comment-019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"obj_member_names\": \"Member-019e44c0-4816-7c9a-9b94-5417d3cbb15f\",\"obj_member_refs\": \"\"," +
diff --git a/roles/tests-unit/files/FWO.Test/NatRuleDisplayHtmlTest.cs b/roles/tests-unit/files/FWO.Test/NatRuleDisplayHtmlTest.cs
new file mode 100644
index 0000000000..2b60a04063
--- /dev/null
+++ b/roles/tests-unit/files/FWO.Test/NatRuleDisplayHtmlTest.cs
@@ -0,0 +1,137 @@
+using FWO.Basics;
+using FWO.Data;
+using FWO.Report;
+using FWO.Report.Filter;
+using FWO.Ui.Display;
+using NUnit.Framework;
+
+namespace FWO.Test
+{
+ [TestFixture]
+ [Parallelizable]
+ internal class NatRuleDisplayHtmlTest
+ {
+ private NatRuleDisplayHtml _display = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _display = new NatRuleDisplayHtml(new SimulatedUserConfig());
+ }
+
+ private static Rule MakeNatRule(NatData natData) =>
+ new Rule { Id = 1, MgmtId = 1, NatData = natData };
+
+ // ── DisplayTranslatedSource ──────────────────────────────────────────
+
+ [Test]
+ public void DisplayTranslatedSource_EmptyFroms_ReturnsEmpty()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedFroms = [] });
+
+ var result = _display.DisplayTranslatedSource(rule, OutputLocation.export);
+
+ Assert.That(result.Trim(), Is.Empty);
+ }
+
+ [Test]
+ public void DisplayTranslatedSource_NegatedWithEmptyFroms_ContainsNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedSourceNegated = true, TranslatedFroms = [] });
+
+ var result = _display.DisplayTranslatedSource(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Contain("not"));
+ }
+
+ [Test]
+ public void DisplayTranslatedSource_NotNegated_DoesNotContainNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedSourceNegated = false, TranslatedFroms = [] });
+
+ var result = _display.DisplayTranslatedSource(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Not.Contain("not"));
+ }
+
+ // ── DisplayTranslatedDestination ─────────────────────────────────────
+
+ [Test]
+ public void DisplayTranslatedDestination_EmptyTos_ReturnsEmpty()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedTos = [] });
+
+ var result = _display.DisplayTranslatedDestination(rule, OutputLocation.export);
+
+ Assert.That(result.Trim(), Is.Empty);
+ }
+
+ [Test]
+ public void DisplayTranslatedDestination_NegatedWithEmptyTos_ContainsNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedDestinationNegated = true, TranslatedTos = [] });
+
+ var result = _display.DisplayTranslatedDestination(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Contain("not"));
+ }
+
+ [Test]
+ public void DisplayTranslatedDestination_NotNegated_DoesNotContainNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedDestinationNegated = false, TranslatedTos = [] });
+
+ var result = _display.DisplayTranslatedDestination(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Not.Contain("not"));
+ }
+
+ // ── DisplayTranslatedService ─────────────────────────────────────────
+
+ [Test]
+ public void DisplayTranslatedService_EmptyServices_ReturnsEmpty()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedServices = [] });
+
+ var result = _display.DisplayTranslatedService(rule, OutputLocation.export);
+
+ Assert.That(result.Trim(), Is.Empty);
+ }
+
+ [Test]
+ public void DisplayTranslatedService_NegatedWithEmptyServices_ContainsNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedServiceNegated = true, TranslatedServices = [] });
+
+ var result = _display.DisplayTranslatedService(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Contain("not"));
+ }
+
+ [Test]
+ public void DisplayTranslatedService_NotNegated_DoesNotContainNegatedText()
+ {
+ var rule = MakeNatRule(new NatData { TranslatedServiceNegated = false, TranslatedServices = [] });
+
+ var result = _display.DisplayTranslatedService(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Not.Contain("not"));
+ }
+
+ [Test]
+ public void DisplayTranslatedService_WithOneService_ContainsServiceName()
+ {
+ var rule = MakeNatRule(new NatData
+ {
+ TranslatedServices =
+ [
+ new ServiceWrapper { Content = new NetworkService { Name = "HTTPS", DestinationPort = 443, Protocol = new() { Id = 6, Name = "TCP" } } }
+ ]
+ });
+
+ var result = _display.DisplayTranslatedService(rule, OutputLocation.export);
+
+ Assert.That(result, Does.Contain("HTTPS"));
+ }
+ }
+}
diff --git a/roles/tests-unit/files/FWO.Test/ReportNatRulesTest.cs b/roles/tests-unit/files/FWO.Test/ReportNatRulesTest.cs
new file mode 100644
index 0000000000..9ce212002a
--- /dev/null
+++ b/roles/tests-unit/files/FWO.Test/ReportNatRulesTest.cs
@@ -0,0 +1,130 @@
+using FWO.Basics;
+using FWO.Data;
+using FWO.Data.Report;
+using FWO.Report;
+using FWO.Report.Filter;
+using FWO.Ui.Display;
+using NUnit.Framework;
+
+namespace FWO.Test
+{
+ [TestFixture]
+ [Parallelizable]
+ internal class ReportNatRulesTest
+ {
+ private NatRuleDisplayHtml _display = null!;
+ private ReportNatRules _report = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ var userConfig = new SimulatedUserConfig();
+ _display = new NatRuleDisplayHtml(userConfig);
+ _report = new ReportNatRules(new DynGraphqlQuery(""), userConfig, ReportType.NatRules);
+ }
+
+ [Test]
+ public void ExportSingleRulebaseToHtml_EmptyRuleArray_ReturnsEmptyString()
+ {
+ var result = _report.ExportSingleRulebaseToHtml([], _display, chapterNumber: 1);
+
+ Assert.That(result.Trim(), Is.Empty);
+ }
+
+ [Test]
+ public void ExportSingleRulebaseToHtml_NormalRule_ContainsTableRow()
+ {
+ var rules = new[]
+ {
+ new Rule
+ {
+ Id = 1,
+ Uid = "rule-uid-1",
+ Name = "TestRule",
+ SectionHeader = "",
+ NatData = new NatData()
+ }
+ };
+
+ var result = _report.ExportSingleRulebaseToHtml(rules, _display, chapterNumber: 1);
+
+ Assert.That(result, Does.Contain(""));
+ Assert.That(result, Does.Contain("
"));
+ }
+
+ [Test]
+ public void ExportSingleRulebaseToHtml_NormalRule_DoesNotContainColspan()
+ {
+ var rules = new[]
+ {
+ new Rule
+ {
+ Id = 1,
+ Uid = "rule-uid-1",
+ SectionHeader = "",
+ NatData = new NatData()
+ }
+ };
+
+ var result = _report.ExportSingleRulebaseToHtml(rules, _display, chapterNumber: 1);
+
+ Assert.That(result, Does.Not.Contain("colspan"));
+ }
+
+ [Test]
+ public void ExportSingleRulebaseToHtml_SectionHeaderRule_ContainsColspan()
+ {
+ var rules = new[]
+ {
+ new Rule
+ {
+ Id = 2,
+ SectionHeader = "My Section",
+ NatData = new NatData()
+ }
+ };
+
+ var result = _report.ExportSingleRulebaseToHtml(rules, _display, chapterNumber: 1);
+
+ Assert.That(result, Does.Contain("colspan"));
+ Assert.That(result, Does.Contain("My Section"));
+ }
+
+ [Test]
+ public void ExportSingleRulebaseToHtml_SectionHeaderRule_DoesNotContainRegularRuleCells()
+ {
+ var rules = new[]
+ {
+ new Rule
+ {
+ Id = 3,
+ SectionHeader = "Section A",
+ NatData = new NatData()
+ }
+ };
+
+ var result = _report.ExportSingleRulebaseToHtml(rules, _display, chapterNumber: 1);
+
+ // A section row gets a single merged cell, no individual for each column
+ Assert.That(result, Does.Not.Contain(" | | "));
+ var tdCount = result.Split(" | ").Length - 1;
+ Assert.That(openTrCount, Is.EqualTo(3));
+ }
+ }
+}
diff --git a/roles/tests-unit/files/FWO.Test/RuleDataTest.cs b/roles/tests-unit/files/FWO.Test/RuleDataTest.cs
index 0fff0cf304..42ebbd72eb 100644
--- a/roles/tests-unit/files/FWO.Test/RuleDataTest.cs
+++ b/roles/tests-unit/files/FWO.Test/RuleDataTest.cs
@@ -156,5 +156,44 @@ public void WorkflowRequestFlowLinkFields_AreDeserialized()
Assert.That(reqTask.Elements[0].FlowServiceObjectId, Is.EqualTo(7004));
Assert.That(reqTask.Elements[0].FlowServiceGroupId, Is.EqualTo(7005));
}
+
+ [Test]
+ public void NormalizedRule_FromRule_PreservesNatAndTranslationFlags()
+ {
+ Rule rule = new()
+ {
+ NatRule = true,
+ AccessRule = false,
+ XlateRule = "1366",
+ TranslatedRule = new Rule { Uid = "translated-rule" },
+ RuleOrderNumber = 7,
+ OrderNumber = 7.0,
+ Disabled = false,
+ SourceNegated = false,
+ Source = "any",
+ SourceRefs = "",
+ DestinationNegated = false,
+ Destination = "any",
+ DestinationRefs = "",
+ ServiceNegated = false,
+ Service = "any",
+ ServiceRefs = "",
+ Action = "accept",
+ Track = "none",
+ Implied = false,
+ Metadata = new RuleMetadata()
+ };
+
+ NormalizedRule normalizedRule = NormalizedRule.FromRule(rule);
+
+ Assert.That(normalizedRule.NatRule, Is.True);
+ Assert.That(normalizedRule.AccessRule, Is.False);
+ Assert.That(normalizedRule.XlateRule, Is.EqualTo("translated-rule"));
+
+ string serialized = JsonConvert.SerializeObject(normalizedRule);
+ Assert.That(serialized, Does.Contain("\"nat_rule\":true"));
+ Assert.That(serialized, Does.Contain("\"access_rule\":false"));
+ Assert.That(serialized, Does.Contain("\"xlate_rule_uid\":\"translated-rule\""));
+ }
}
}
|