Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,4 +54,4 @@ public void error(String format, Object... args) {
logger.error(message);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems fairly long; please check whether it's feasible to refactor into multiple focused methods.

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");
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is quite long; can this be split into multiple methods?

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<JsonNode> requiredTags = dastCorrTag != null
? List.of(predictionTag, statusTag, dastCorrTag)
: List.of(predictionTag, statusTag);
// Build list of tags requiring manual association (excludes system-managed tags)
List<SynchronizationResult> allResults = List.of(predictionResult, statusResult, dastCorrResult);
List<JsonNode> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static List<String> validatePreUpload(UnirestInstance unirest, String ver
*/
private static void emitWarnings(List<String> warnings, IAviatorLogger logger) {
for (String warning : warnings) {
logger.progress(warning);
logger.warn(warning);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was mentioned that these system-managed tags are only available if Aviator is enabled in SSC configuration, correct? If so, would be good to explicitly mention this in fcli usage help.

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.
Expand Down
Loading
Loading