From 526e5879dee03a7ed16abe9d35c9dc793cce30c5 Mon Sep 17 00:00:00 2001 From: Neeta Meshram Date: Tue, 2 Jun 2026 15:06:18 +0530 Subject: [PATCH 1/4] Added reflectable for native EXE --- .../com/fortify/cli/aviator/ssc/helper/AviatorSSCTagDefs.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagDefs.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagDefs.java index b24379388c4..399c2f58e0e 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagDefs.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagDefs.java @@ -14,12 +14,15 @@ import java.util.List; +import com.formkiq.graalvm.annotations.Reflectable; + import lombok.Getter; import lombok.RequiredArgsConstructor; public final class AviatorSSCTagDefs { @Getter @RequiredArgsConstructor + @Reflectable public static final class TagDefinition { private final String guid; private final String name; From 96c5a1a647cae5e0e9f138ce6eed2ab019d13219 Mon Sep 17 00:00:00 2001 From: Neeta Meshram Date: Tue, 2 Jun 2026 15:51:15 +0530 Subject: [PATCH 2/4] Used logger.warn instead of logger.progress --- .../fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java index 74b780b8f02..0aceafb485d 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java @@ -107,7 +107,8 @@ public static List validatePreUpload(UnirestInstance unirest, String ver */ private static void emitWarnings(List warnings, IAviatorLogger logger) { for (String warning : warnings) { - logger.progress(warning); + logger.warn(warning); + //logger.progress(warning); } } From e1a579fc18ce50e0340135468617db63d9bee9d6 Mon Sep 17 00:00:00 2001 From: Neeta Meshram Date: Wed, 3 Jun 2026 16:00:43 +0530 Subject: [PATCH 3/4] feat: Add capability detection for SSC 26.2+ system-managed Aviator tags - Add SynchronizationResult class to track tag sync status and system-managed flag - Implement /internalCustomTags fallback for Aviator built-in tags detection - Update prepare command to skip manual association for system-managed tags - Preserve DAST correlation tag and last_correlation attribute behavior - Fix AviatorLoggerImpl.warn() to output warnings to console (stderr) - Update help text to explain SSC 26.2+ behavior - Add unit tests for SynchronizationResult and PrepareResult --- .../cli/aviator/config/AviatorLoggerImpl.java | 6 +- .../ssc/helper/AviatorSSCCustomTagHelper.java | 95 +++++++++- .../ssc/helper/AviatorSSCPrepareHelper.java | 33 +++- .../ssc/helper/AviatorSSCTagValidator.java | 1 - .../aviator/i18n/AviatorMessages.properties | 7 +- .../helper/AviatorSSCCustomTagHelperTest.java | 169 ++++++++++++++++++ 6 files changed, 293 insertions(+), 18 deletions(-) create mode 100644 fcli-core/fcli-aviator/src/test/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelperTest.java diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/config/AviatorLoggerImpl.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/config/AviatorLoggerImpl.java index d52bff8ac87..e918a691660 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/config/AviatorLoggerImpl.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/config/AviatorLoggerImpl.java @@ -39,7 +39,9 @@ public void info(String format, Object... args) { @Override public void warn(String format, Object... args) { - logger.warn(format, args); + String message = String.format(format, args); + progressWriter.writeWarning(message); // Console (stderr) + logger.warn(message); } @Override @@ -52,4 +54,4 @@ public void error(String format, Object... args) { logger.error(message); } } -} \ No newline at end of file +} diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java index 9b12d52f494..7a81035807c 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java @@ -22,12 +22,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.formkiq.graalvm.annotations.Reflectable; import com.fortify.cli.aviator.ssc.helper.AviatorSSCTagDefs.TagDefinition; import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.rest.unirest.UnexpectedHttpResponseException; import com.fortify.cli.ssc._common.rest.ssc.SSCUrls; import kong.unirest.UnirestInstance; +import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -36,7 +38,42 @@ public class AviatorSSCCustomTagHelper { private final UnirestInstance unirest; private final TagDefinition tagDef; - public JsonNode synchronize(AviatorSSCPrepareHelper.PrepareResult result) { + /** + * Result of tag synchronization, indicating whether the tag was found/created + * and whether it's system-managed (SSC 26.2+ built-in) or requires manual association. + */ + @Getter @RequiredArgsConstructor @Reflectable + public static final class SynchronizationResult { + private final JsonNode tag; + private final boolean successful; + private final boolean systemManaged; + + /** + * Returns true if the tag was successfully synchronized and requires manual + * association with issue templates/app versions. System-managed tags (SSC 26.2+ + * built-in Aviator tags) are automatically associated and don't need manual setup. + */ + public boolean requiresAssociation() { + return successful && !systemManaged; + } + + public static SynchronizationResult success(JsonNode tag, boolean systemManaged) { + return new SynchronizationResult(tag, true, systemManaged); + } + + public static SynchronizationResult failure() { + return new SynchronizationResult(null, false, false); + } + } + + /** + * Synchronizes the custom tag with SSC. For Aviator built-in tags (prediction/status), + * also checks the /internalCustomTags endpoint to detect SSC 26.2+ system-managed tags. + * + * @param result the prepare result to record status entries + * @return SynchronizationResult indicating success/failure and whether system-managed + */ + public SynchronizationResult synchronize(AviatorSSCPrepareHelper.PrepareResult result) { try { LOG.debug("Searching for custom tag '{}' (GUID: {})", tagDef.getName(), tagDef.getGuid()); ArrayNode customTags = (ArrayNode) unirest.get(SSCUrls.CUSTOM_TAGS).asObject(JsonNode.class).getBody().get("data"); @@ -45,13 +82,63 @@ public JsonNode synchronize(AviatorSSCPrepareHelper.PrepareResult result) { .findFirst().orElse(null); if (existingTag != null) { - return verifyAndUpdateExistingTag(result, existingTag); - } else { - return createNewTag(result); + JsonNode updatedTag = verifyAndUpdateExistingTag(result, existingTag); + return SynchronizationResult.success(updatedTag, false); } + + // Tag not found in /customTags - check if it's a system-managed Aviator tag + if (isAviatorBuiltInTag()) { + JsonNode internalTag = findInInternalCustomTags(); + if (internalTag != null) { + LOG.info("Tag '{}' found as system-managed internal tag (SSC 26.2+). Configure through SSC.", + tagDef.getName()); + result.addEntry("Custom Tag", "SYSTEM_MANAGED", + "'" + tagDef.getName() + "' is a built-in SSC tag (SSC 26.2+). Configure through SSC."); + return SynchronizationResult.success(internalTag, true); + } + } + + // Tag not found anywhere - create it + JsonNode createdTag = createNewTag(result); + return createdTag != null + ? SynchronizationResult.success(createdTag, false) + : SynchronizationResult.failure(); } catch (UnexpectedHttpResponseException e) { LOG.error("Error synchronizing custom tag '{}': {}", tagDef.getName(), e.getMessage()); result.addEntry("Custom Tag", "FAILED", "Error synchronizing tag '" + tagDef.getName() + "': " + e.getMessage()); + return SynchronizationResult.failure(); + } + } + + /** + * Returns true if this tag is an Aviator built-in tag that may be system-managed in SSC 26.2+. + * DAST correlation tag is NOT an Aviator built-in - it's always a custom tag created by fcli. + */ + private boolean isAviatorBuiltInTag() { + return tagDef == AviatorSSCTagDefs.AVIATOR_PREDICTION_TAG + || tagDef == AviatorSSCTagDefs.AVIATOR_STATUS_TAG; + } + + /** + * Queries the /internalCustomTags endpoint to find system-managed Aviator tags. + * Returns null if the endpoint doesn't exist (older SSC) or tag not found. + */ + private JsonNode findInInternalCustomTags() { + try { + LOG.debug("Checking /internalCustomTags for system-managed tag '{}'", tagDef.getName()); + JsonNode response = unirest.get(SSCUrls.INTERNAL_CUSTOM_TAGS) + .asObject(JsonNode.class).getBody(); + JsonNode data = response.get("data"); + if (data == null || !data.isArray()) { + LOG.debug("No data array in /internalCustomTags response"); + return null; + } + return JsonHelper.stream((ArrayNode) data) + .filter(tag -> tagDef.getGuid().equalsIgnoreCase(tag.path("guid").asText())) + .findFirst().orElse(null); + } catch (UnexpectedHttpResponseException e) { + // Endpoint may not exist in older SSC versions - this is expected + LOG.debug("Could not query /internalCustomTags (may not exist in this SSC version): {}", e.getMessage()); return null; } } diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java index 5db1cddca22..a3a5450128b 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java @@ -14,9 +14,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.aviator.ssc.helper.AviatorSSCCustomTagHelper.SynchronizationResult; import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.progress.helper.IProgressWriter; import com.fortify.cli.common.progress.helper.ProgressWriterType; @@ -70,27 +72,40 @@ public PrepareResult prepare(PrepareOptions options) { var tagHelperStatus = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.AVIATOR_STATUS_TAG); var tagHelperDastCorr = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.DAST_CORRELATION_STATUS_TAG); - JsonNode predictionTag = tagHelperPrediction.synchronize(result); - JsonNode statusTag = tagHelperStatus.synchronize(result); - JsonNode dastCorrTag = tagHelperDastCorr.synchronize(result); + SynchronizationResult predictionResult = tagHelperPrediction.synchronize(result); + SynchronizationResult statusResult = tagHelperStatus.synchronize(result); + SynchronizationResult dastCorrResult = tagHelperDastCorr.synchronize(result); - if (predictionTag == null || statusTag == null) { + // Required Aviator tags must succeed (either as custom or system-managed) + if (!predictionResult.isSuccessful() || !statusResult.isSuccessful()) { result.addEntry("Global", "HALTED", "Failed to synchronize one or more required Aviator custom tags."); return result; } - if (dastCorrTag == null) { + // DAST correlation tag is optional - just warn if it fails + if (!dastCorrResult.isSuccessful()) { result.addEntry("DAST Correlation Tag", "WARNING", "Failed to synchronize 'DAST correlation status' tag. SAST-DAST correlation feature may not be fully visible in SSC UI."); } + // Always synchronize attributes (before any early return) progress.writeProgress("Synchronizing Aviator custom attributes..."); new AviatorSSCCorrelationAttributeHelper(unirest, AviatorSSCCorrelationAttributeDefs.LAST_CORRELATION_ATTR) .synchronize(result); - // Build required tags list — include dastCorrTag only if successfully synchronized - List requiredTags = dastCorrTag != null - ? List.of(predictionTag, statusTag, dastCorrTag) - : List.of(predictionTag, statusTag); + // Build list of tags requiring manual association (excludes system-managed tags) + List allResults = List.of(predictionResult, statusResult, dastCorrResult); + List requiredTags = allResults.stream() + .filter(SynchronizationResult::requiresAssociation) + .map(SynchronizationResult::getTag) + .collect(Collectors.toList()); + + // If all Aviator tags are system-managed (SSC 26.2+), skip template/version association + if (requiredTags.isEmpty()) { + result.addEntry("Global", "INFO", + "All Aviator tags are system-managed (SSC 26.2+). No manual template/version association required."); + progress.writeInfo("All Aviator tags are system-managed by SSC. No manual association needed."); + return result; + } if (options.isAllIssueTemplates() || options.getIssueTemplateNameOrId() != null) { new AviatorSSCTemplateUpdater(unirest).process(options, result, requiredTags, progress); diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java index 0aceafb485d..8130310d1d5 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCTagValidator.java @@ -108,7 +108,6 @@ public static List validatePreUpload(UnirestInstance unirest, String ver private static void emitWarnings(List warnings, IAviatorLogger logger) { for (String warning : warnings) { logger.warn(warning); - //logger.progress(warning); } } diff --git a/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties b/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties index bda5ffd50c0..7c6f20c681b 100644 --- a/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties +++ b/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties @@ -161,10 +161,13 @@ fcli.aviator.ssc.apply-remediations.since = Filter artifacts by upload date. Sup Can only be used with --latest or --all; not compatible with --artifact-id. fcli.aviator.ssc.prepare.usage.header = (PREVIEW) Prepare an SSC instance for Fortify Remediation Aviator integration. -fcli.aviator.ssc.prepare.usage.description = This command ensures that the Fortify Remediation Aviator-specific custom tags ('Aviator prediction',`Aviator status`) \ +fcli.aviator.ssc.prepare.usage.description = This command ensures that the Fortify Remediation Aviator-specific custom tags ('Aviator prediction', 'Aviator status') \ exist in SSC and are associated with the specified issue templates and/or application versions. \ This is necessary to prevent SSC from stripping Fortify Remediation Aviator audit data from uploaded FPR files. \ - At least one update option must be specified. \ + %n%nFor SSC 26.2 and later, Aviator tags are built-in system-managed tags that are automatically associated with all \ + issue templates and application versions. In this case, the command will detect the system-managed tags and skip \ + manual association. For older SSC versions, the tags are created as custom tags and must be manually associated. \ + %n%nAt least one update option must be specified. \ %n%nNOTE\: This command is considered preview functionality as it will likely change in a future fcli version \ to re-use generic tag update functionality that is planned for the fcli ssc module. fcli.aviator.ssc.prepare.issue-template = Update a single issue template by its name or ID. diff --git a/fcli-core/fcli-aviator/src/test/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelperTest.java b/fcli-core/fcli-aviator/src/test/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelperTest.java new file mode 100644 index 00000000000..e83531d111a --- /dev/null +++ b/fcli-core/fcli-aviator/src/test/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelperTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.aviator.ssc.helper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fortify.cli.aviator.ssc.helper.AviatorSSCCustomTagHelper.SynchronizationResult; + +/** + * Unit tests for {@link AviatorSSCCustomTagHelper} capability detection + * for SSC 26.2+ system-managed Aviator tags. + * + *

These tests focus on the {@link SynchronizationResult} model class behavior + * since the actual HTTP calls require a live SSC instance or mock framework. + */ +class AviatorSSCCustomTagHelperTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Nested + @DisplayName("SynchronizationResult") + class SynchronizationResultTests { + + @Test + @DisplayName("success with non-system-managed tag should require association") + void successNonSystemManaged_requiresAssociation() { + JsonNode tag = MAPPER.createObjectNode().put("id", "123").put("name", "Aviator prediction"); + SynchronizationResult result = SynchronizationResult.success(tag, false); + + assertTrue(result.isSuccessful(), "Should be successful"); + assertFalse(result.isSystemManaged(), "Should NOT be system-managed"); + assertTrue(result.requiresAssociation(), "Non-system-managed tag should require association"); + assertNotNull(result.getTag(), "Tag should not be null"); + assertEquals("123", result.getTag().get("id").asText()); + } + + @Test + @DisplayName("success with system-managed tag should NOT require association") + void successSystemManaged_doesNotRequireAssociation() { + JsonNode tag = MAPPER.createObjectNode().put("id", "456").put("name", "Aviator status"); + SynchronizationResult result = SynchronizationResult.success(tag, true); + + assertTrue(result.isSuccessful(), "Should be successful"); + assertTrue(result.isSystemManaged(), "Should be system-managed"); + assertFalse(result.requiresAssociation(), "System-managed tag should NOT require association"); + assertNotNull(result.getTag(), "Tag should not be null"); + } + + @Test + @DisplayName("failure should not require association and have null tag") + void failure_doesNotRequireAssociation() { + SynchronizationResult result = SynchronizationResult.failure(); + + assertFalse(result.isSuccessful(), "Should NOT be successful"); + assertFalse(result.isSystemManaged(), "Failure should not be system-managed"); + assertFalse(result.requiresAssociation(), "Failed sync should NOT require association"); + assertNull(result.getTag(), "Failed sync should have null tag"); + } + + @Test + @DisplayName("requiresAssociation is false when not successful even if not system-managed") + void notSuccessful_doesNotRequireAssociation() { + // Edge case: construct manually with successful=false but systemManaged=false + SynchronizationResult result = new SynchronizationResult(null, false, false); + + assertFalse(result.requiresAssociation(), + "Should NOT require association when not successful, regardless of systemManaged flag"); + } + } + + @Nested + @DisplayName("PrepareResult entry tracking") + class PrepareResultTests { + + @Test + @DisplayName("PrepareResult correctly tracks entries") + void prepareResult_tracksEntries() { + var result = new AviatorSSCPrepareHelper.PrepareResult(); + + result.addEntry("Custom Tag", "VERIFIED", "Tag 'Aviator prediction' is already present."); + result.addEntry("Custom Tag", "SYSTEM_MANAGED", "'Aviator status' is a built-in SSC tag."); + result.addEntry("Global", "INFO", "All Aviator tags are system-managed."); + + assertEquals(3, result.getEntries().size()); + + JsonNode json = result.toJsonNode(); + assertTrue(json.isArray()); + assertEquals(3, json.size()); + assertEquals("VERIFIED", json.get(0).get("status").asText()); + assertEquals("SYSTEM_MANAGED", json.get(1).get("status").asText()); + assertEquals("INFO", json.get(2).get("status").asText()); + } + + @Test + @DisplayName("PrepareResult toJsonNode includes all fields") + void prepareResult_toJsonNode_includesAllFields() { + var result = new AviatorSSCPrepareHelper.PrepareResult(); + result.addEntry("Custom Tag", "CREATED", "Tag 'DAST correlation status' created."); + + JsonNode json = result.toJsonNode(); + JsonNode entry = json.get(0); + + assertEquals("CREATED", entry.get("status").asText()); + assertEquals("Custom Tag", entry.get("entity").asText()); + assertEquals("Tag 'DAST correlation status' created.", entry.get("details").asText()); + } + } + + @Nested + @DisplayName("TagDefinition behavior") + class TagDefinitionTests { + + @Test + @DisplayName("AVIATOR_PREDICTION_TAG has correct GUID and values") + void aviatorPredictionTag_hasCorrectGuidAndValues() { + var tag = AviatorSSCTagDefs.AVIATOR_PREDICTION_TAG; + + assertEquals("C2D6EC66-CCB3-4FB9-9EE0-0BB02F51008F", tag.getGuid()); + assertEquals("Aviator prediction", tag.getName()); + assertEquals("LIST", tag.getValueType()); + assertEquals(6, tag.getValues().size()); + assertTrue(tag.getValues().contains("AVIATOR:Not an Issue")); + assertTrue(tag.getValues().contains("AVIATOR:Remediation Required")); + } + + @Test + @DisplayName("AVIATOR_STATUS_TAG has correct GUID and values") + void aviatorStatusTag_hasCorrectGuidAndValues() { + var tag = AviatorSSCTagDefs.AVIATOR_STATUS_TAG; + + assertEquals("FB7B0462-2C2E-46D9-811A-DCC1F3C83051", tag.getGuid()); + assertEquals("Aviator status", tag.getName()); + assertEquals("LIST", tag.getValueType()); + assertEquals(1, tag.getValues().size()); + assertTrue(tag.getValues().contains("PROCESSED_BY_AVIATOR")); + } + + @Test + @DisplayName("DAST_CORRELATION_STATUS_TAG is a TEXT type with no predefined values") + void dastCorrelationTag_isTextType() { + var tag = AviatorSSCTagDefs.DAST_CORRELATION_STATUS_TAG; + + assertEquals("7A3B5C9D-1E2F-4A8B-9C0D-E1F2A3B4C5D6", tag.getGuid()); + assertEquals("DAST correlation status", tag.getName()); + assertEquals("TEXT", tag.getValueType()); + assertTrue(tag.getValues().isEmpty(), "TEXT tags should have empty value list"); + } + } +} From e3712cc4bde12210d09be02c8eced26e34c220af Mon Sep 17 00:00:00 2001 From: Neeta Meshram Date: Fri, 5 Jun 2026 11:52:09 +0530 Subject: [PATCH 4/4] Refactored long methods --- .../ssc/helper/AviatorSSCCustomTagHelper.java | 86 ++++++++---- .../ssc/helper/AviatorSSCPrepareHelper.java | 125 ++++++++++++------ .../aviator/i18n/AviatorMessages.properties | 5 +- 3 files changed, 147 insertions(+), 69 deletions(-) diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java index 7a81035807c..e8279023ff2 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCCustomTagHelper.java @@ -75,39 +75,73 @@ public static SynchronizationResult failure() { */ public SynchronizationResult synchronize(AviatorSSCPrepareHelper.PrepareResult result) { try { - LOG.debug("Searching for custom tag '{}' (GUID: {})", tagDef.getName(), tagDef.getGuid()); - ArrayNode customTags = (ArrayNode) unirest.get(SSCUrls.CUSTOM_TAGS).asObject(JsonNode.class).getBody().get("data"); - JsonNode existingTag = JsonHelper.stream(customTags) - .filter(tag -> tagDef.getGuid().equals(tag.get("guid").asText())) - .findFirst().orElse(null); - + JsonNode existingTag = findExistingCustomTag(); if (existingTag != null) { - JsonNode updatedTag = verifyAndUpdateExistingTag(result, existingTag); - return SynchronizationResult.success(updatedTag, false); + return handleExistingTag(existingTag, result); } - // Tag not found in /customTags - check if it's a system-managed Aviator tag - if (isAviatorBuiltInTag()) { - JsonNode internalTag = findInInternalCustomTags(); - if (internalTag != null) { - LOG.info("Tag '{}' found as system-managed internal tag (SSC 26.2+). Configure through SSC.", - tagDef.getName()); - result.addEntry("Custom Tag", "SYSTEM_MANAGED", - "'" + tagDef.getName() + "' is a built-in SSC tag (SSC 26.2+). Configure through SSC."); - return SynchronizationResult.success(internalTag, true); - } + SynchronizationResult systemManagedResult = checkSystemManagedTag(result); + if (systemManagedResult != null) { + return systemManagedResult; } - // Tag not found anywhere - create it - JsonNode createdTag = createNewTag(result); - return createdTag != null - ? SynchronizationResult.success(createdTag, false) - : SynchronizationResult.failure(); + return createTagOrFail(result); } catch (UnexpectedHttpResponseException e) { - LOG.error("Error synchronizing custom tag '{}': {}", tagDef.getName(), e.getMessage()); - result.addEntry("Custom Tag", "FAILED", "Error synchronizing tag '" + tagDef.getName() + "': " + e.getMessage()); - return SynchronizationResult.failure(); + return handleSynchronizationError(e, result); + } + } + + /** Searches for an existing custom tag by GUID in /customTags endpoint. */ + private JsonNode findExistingCustomTag() { + LOG.debug("Searching for custom tag '{}' (GUID: {})", tagDef.getName(), tagDef.getGuid()); + ArrayNode customTags = (ArrayNode) unirest.get(SSCUrls.CUSTOM_TAGS) + .asObject(JsonNode.class).getBody().get("data"); + return JsonHelper.stream(customTags) + .filter(tag -> tagDef.getGuid().equals(tag.get("guid").asText())) + .findFirst().orElse(null); + } + + /** Handles an existing custom tag by verifying/updating it. */ + private SynchronizationResult handleExistingTag(JsonNode existingTag, + AviatorSSCPrepareHelper.PrepareResult result) { + JsonNode updatedTag = verifyAndUpdateExistingTag(result, existingTag); + return SynchronizationResult.success(updatedTag, false); + } + + /** + * Checks if this is a system-managed Aviator tag (SSC 26.2+). + * Returns SynchronizationResult if found, null otherwise. + */ + private SynchronizationResult checkSystemManagedTag(AviatorSSCPrepareHelper.PrepareResult result) { + if (!isAviatorBuiltInTag()) { + return null; + } + JsonNode internalTag = findInInternalCustomTags(); + if (internalTag == null) { + return null; } + LOG.info("Tag '{}' found as system-managed internal tag (SSC 26.2+). Configure through SSC.", + tagDef.getName()); + result.addEntry("Custom Tag", "SYSTEM_MANAGED", + "'" + tagDef.getName() + "' is a built-in SSC tag (SSC 26.2+). Configure through SSC."); + return SynchronizationResult.success(internalTag, true); + } + + /** Creates a new tag or returns failure if creation fails. */ + private SynchronizationResult createTagOrFail(AviatorSSCPrepareHelper.PrepareResult result) { + JsonNode createdTag = createNewTag(result); + return createdTag != null + ? SynchronizationResult.success(createdTag, false) + : SynchronizationResult.failure(); + } + + /** Handles synchronization errors by logging and recording the failure. */ + private SynchronizationResult handleSynchronizationError(UnexpectedHttpResponseException e, + AviatorSSCPrepareHelper.PrepareResult result) { + LOG.error("Error synchronizing custom tag '{}': {}", tagDef.getName(), e.getMessage()); + result.addEntry("Custom Tag", "FAILED", + "Error synchronizing tag '" + tagDef.getName() + "': " + e.getMessage()); + return SynchronizationResult.failure(); } /** diff --git a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java index a3a5450128b..56f96f09e03 100644 --- a/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java +++ b/fcli-core/fcli-aviator/src/main/java/com/fortify/cli/aviator/ssc/helper/AviatorSSCPrepareHelper.java @@ -63,58 +63,101 @@ public static class ResultCounter { public void incrementSkipped() { this.skipped++; } } + /** Groups the synchronization results of the three Aviator custom tags. */ + private record TagSynchronizationResults( + SynchronizationResult prediction, + SynchronizationResult status, + SynchronizationResult dastCorrelation + ) {} + public PrepareResult prepare(PrepareOptions options) { PrepareResult result = new PrepareResult(); try (IProgressWriter progress = ProgressWriterType.auto.create()) { + TagSynchronizationResults tagResults = synchronizeTags(result, progress); - progress.writeProgress("Synchronizing Aviator custom tags..."); - var tagHelperPrediction = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.AVIATOR_PREDICTION_TAG); - var tagHelperStatus = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.AVIATOR_STATUS_TAG); - var tagHelperDastCorr = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.DAST_CORRELATION_STATUS_TAG); - - SynchronizationResult predictionResult = tagHelperPrediction.synchronize(result); - SynchronizationResult statusResult = tagHelperStatus.synchronize(result); - SynchronizationResult dastCorrResult = tagHelperDastCorr.synchronize(result); - - // Required Aviator tags must succeed (either as custom or system-managed) - if (!predictionResult.isSuccessful() || !statusResult.isSuccessful()) { - result.addEntry("Global", "HALTED", "Failed to synchronize one or more required Aviator custom tags."); + if (haltIfRequiredTagsFailed(tagResults, result)) { return result; } - // DAST correlation tag is optional - just warn if it fails - if (!dastCorrResult.isSuccessful()) { - result.addEntry("DAST Correlation Tag", "WARNING", - "Failed to synchronize 'DAST correlation status' tag. SAST-DAST correlation feature may not be fully visible in SSC UI."); - } + addOptionalTagWarnings(tagResults, result); - // Always synchronize attributes (before any early return) - progress.writeProgress("Synchronizing Aviator custom attributes..."); - new AviatorSSCCorrelationAttributeHelper(unirest, AviatorSSCCorrelationAttributeDefs.LAST_CORRELATION_ATTR) - .synchronize(result); - - // Build list of tags requiring manual association (excludes system-managed tags) - List allResults = List.of(predictionResult, statusResult, dastCorrResult); - List requiredTags = allResults.stream() - .filter(SynchronizationResult::requiresAssociation) - .map(SynchronizationResult::getTag) - .collect(Collectors.toList()); - - // If all Aviator tags are system-managed (SSC 26.2+), skip template/version association - if (requiredTags.isEmpty()) { - result.addEntry("Global", "INFO", - "All Aviator tags are system-managed (SSC 26.2+). No manual template/version association required."); - progress.writeInfo("All Aviator tags are system-managed by SSC. No manual association needed."); - return result; - } + synchronizeAttributes(result, progress); - if (options.isAllIssueTemplates() || options.getIssueTemplateNameOrId() != null) { - new AviatorSSCTemplateUpdater(unirest).process(options, result, requiredTags, progress); + List tagsRequiringAssociation = getTagsRequiringAssociation(tagResults); + if (handleNoManualAssociationRequired(tagsRequiringAssociation, result, progress)) { + return result; } - if (options.isAllAppVersions() || options.getAppVersionNameOrId() != null) { - new AviatorSSCAppVersionUpdater(unirest).process(options, result, requiredTags, progress); - } + updateTargets(options, result, tagsRequiringAssociation, progress); } return result; } + + /** Synchronizes all three Aviator custom tags (prediction, status, DAST correlation). */ + private TagSynchronizationResults synchronizeTags(PrepareResult result, IProgressWriter progress) { + progress.writeProgress("Synchronizing Aviator custom tags..."); + var tagHelperPrediction = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.AVIATOR_PREDICTION_TAG); + var tagHelperStatus = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.AVIATOR_STATUS_TAG); + var tagHelperDastCorr = new AviatorSSCCustomTagHelper(unirest, AviatorSSCTagDefs.DAST_CORRELATION_STATUS_TAG); + + return new TagSynchronizationResults( + tagHelperPrediction.synchronize(result), + tagHelperStatus.synchronize(result), + tagHelperDastCorr.synchronize(result) + ); + } + + /** Returns true if required Aviator tags failed to synchronize, adding a HALTED entry. */ + private boolean haltIfRequiredTagsFailed(TagSynchronizationResults tagResults, PrepareResult result) { + if (!tagResults.prediction().isSuccessful() || !tagResults.status().isSuccessful()) { + result.addEntry("Global", "HALTED", "Failed to synchronize one or more required Aviator custom tags."); + return true; + } + return false; + } + + /** Adds warnings for optional tags that failed to synchronize. */ + private void addOptionalTagWarnings(TagSynchronizationResults tagResults, PrepareResult result) { + if (!tagResults.dastCorrelation().isSuccessful()) { + result.addEntry("DAST Correlation Tag", "WARNING", + "Failed to synchronize 'DAST correlation status' tag. SAST-DAST correlation feature may not be fully visible in SSC UI."); + } + } + + /** Synchronizes Aviator custom attributes. */ + private void synchronizeAttributes(PrepareResult result, IProgressWriter progress) { + progress.writeProgress("Synchronizing Aviator custom attributes..."); + new AviatorSSCCorrelationAttributeHelper(unirest, AviatorSSCCorrelationAttributeDefs.LAST_CORRELATION_ATTR) + .synchronize(result); + } + + /** Returns tags that require manual association (excludes system-managed tags). */ + private List getTagsRequiringAssociation(TagSynchronizationResults tagResults) { + return List.of(tagResults.prediction(), tagResults.status(), tagResults.dastCorrelation()).stream() + .filter(SynchronizationResult::requiresAssociation) + .map(SynchronizationResult::getTag) + .collect(Collectors.toList()); + } + + /** Returns true if all tags are system-managed and no manual association is needed. */ + private boolean handleNoManualAssociationRequired(List tagsRequiringAssociation, + PrepareResult result, IProgressWriter progress) { + if (tagsRequiringAssociation.isEmpty()) { + result.addEntry("Global", "INFO", + "All Aviator tags are system-managed (SSC 26.2+). No manual template/version association required."); + progress.writeInfo("All Aviator tags are system-managed by SSC. No manual association needed."); + return true; + } + return false; + } + + /** Updates issue templates and/or app versions based on options. */ + private void updateTargets(PrepareOptions options, PrepareResult result, + List tagsRequiringAssociation, IProgressWriter progress) { + if (options.isAllIssueTemplates() || options.getIssueTemplateNameOrId() != null) { + new AviatorSSCTemplateUpdater(unirest).process(options, result, tagsRequiringAssociation, progress); + } + if (options.isAllAppVersions() || options.getAppVersionNameOrId() != null) { + new AviatorSSCAppVersionUpdater(unirest).process(options, result, tagsRequiringAssociation, progress); + } + } } diff --git a/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties b/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties index 7c6f20c681b..84f1a0f4ea4 100644 --- a/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties +++ b/fcli-core/fcli-aviator/src/main/resources/com/fortify/cli/aviator/i18n/AviatorMessages.properties @@ -164,8 +164,9 @@ fcli.aviator.ssc.prepare.usage.header = (PREVIEW) Prepare an SSC instance for Fo fcli.aviator.ssc.prepare.usage.description = This command ensures that the Fortify Remediation Aviator-specific custom tags ('Aviator prediction', 'Aviator status') \ exist in SSC and are associated with the specified issue templates and/or application versions. \ This is necessary to prevent SSC from stripping Fortify Remediation Aviator audit data from uploaded FPR files. \ - %n%nFor SSC 26.2 and later, Aviator tags are built-in system-managed tags that are automatically associated with all \ - issue templates and application versions. In this case, the command will detect the system-managed tags and skip \ + For SSC 26.2 and later, Aviator tags are built-in system-managed tags that can be enabled through \ + Administration -> Configuration -> AI Assistant -> Aviator. When enabled, the tags are automatically \ + associated with all issue templates and application versions, and this command will detect them and skip \ manual association. For older SSC versions, the tags are created as custom tags and must be manually associated. \ %n%nAt least one update option must be specified. \ %n%nNOTE\: This command is considered preview functionality as it will likely change in a future fcli version \