From ca7f5420ec58c4958c8de4077adb358f07b91cca Mon Sep 17 00:00:00 2001 From: mjain6 Date: Thu, 16 Apr 2026 10:43:03 +0530 Subject: [PATCH 1/2] Added issue get command for FoD and SSC --- .../fod/issue/cli/cmd/FoDIssueCommands.java | 1 + .../fod/issue/cli/cmd/FoDIssueGetCommand.java | 87 +++++++++++++++++++ .../cli/fod/i18n/FoDMessages.properties | 3 + .../ssc/issue/cli/cmd/SSCIssueCommands.java | 1 + .../ssc/issue/cli/cmd/SSCIssueGetCommand.java | 56 ++++++++++++ .../issue/cli/mixin/SSCIssueIncludeMixin.java | 2 +- .../cli/ssc/i18n/SSCMessages.properties | 10 ++- 7 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java create mode 100644 fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueGetCommand.java diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueCommands.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueCommands.java index 3fa8be83e6d..cda827f0873 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueCommands.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueCommands.java @@ -19,6 +19,7 @@ @CommandLine.Command(name = "issue", subcommands = { FoDIssueListCommand.class, + FoDIssueGetCommand.class, FoDIssueUpdateCommand.class, } ) diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java new file mode 100644 index 00000000000..7e10799e2f2 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java @@ -0,0 +1,87 @@ +/* + * 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.fod.issue.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.exception.FcliSimpleException; +import com.fortify.cli.common.json.producer.IObjectNodeProducer; +import com.fortify.cli.common.json.producer.ObjectNodeProducerApplyFrom; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.util.DisableTest; +import com.fortify.cli.common.util.DisableTest.TestType; +import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; +import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDOutputCommand; +import com.fortify.cli.fod._common.rest.FoDUrls; +import com.fortify.cli.fod._common.rest.helper.FoDInputTransformer; +import com.fortify.cli.fod.issue.cli.mixin.FoDIssueEmbedMixin; +import com.fortify.cli.fod.issue.cli.mixin.FoDIssueIncludeMixin; +import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin; + +import kong.unirest.HttpRequest; +import kong.unirest.UnirestInstance; +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Parameters; + +@DisableTest(TestType.CMD_DEFAULT_TABLE_OPTIONS_PRESENT) +@Command(name = OutputHelperMixins.Get.CMD_NAME) +public class FoDIssueGetCommand extends AbstractFoDOutputCommand { + @Getter @Mixin private OutputHelperMixins.Get outputHelper; + @Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins + @Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver; + @Parameters(index = "0", arity = "1", descriptionKey = "fcli.fod.issue.get.id") + private String vulnId; + @Mixin private FoDIssueEmbedMixin embedMixin; + @Mixin private FoDIssueIncludeMixin includeMixin; + + @Override + protected IObjectNodeProducer getObjectNodeProducer(UnirestInstance unirest) { + String releaseId = releaseResolver.getReleaseId(unirest); + JsonNode issue = getIssue(unirest, releaseId); + return simpleObjectNodeProducerBuilder(ObjectNodeProducerApplyFrom.SPEC) + .source(issue) + .build(); + } + + private JsonNode getIssue(UnirestInstance unirest, String releaseId) { + boolean numericId = vulnId!=null && vulnId.chars().allMatch(Character::isDigit); + JsonNode issue = numericId + ? getIssueByFilter(unirest, releaseId, "id", vulnId) + : getIssueByFilter(unirest, releaseId, "vulnId", vulnId); + if ( issue==null ) { + issue = numericId + ? getIssueByFilter(unirest, releaseId, "vulnId", vulnId) + : getIssueByFilter(unirest, releaseId, "id", vulnId); + } + if ( issue==null ) { + throw new FcliSimpleException(String.format("No issue found for id or vulnId '%s' in the specified release", vulnId)); + } + return issue; + } + + private JsonNode getIssueByFilter(UnirestInstance unirest, String releaseId, String fieldName, String value) { + HttpRequest request = unirest.get(FoDUrls.VULNERABILITIES) + .routeParam("relId", releaseId) + .queryString("filters", fieldName+":"+value) + .queryString("limit", "1"); + JsonNode body = includeMixin.updateRequest(request).asObject(JsonNode.class).getBody(); + JsonNode items = FoDInputTransformer.getItems(body); + return items!=null && items.isArray() && !items.isEmpty() ? items.get(0) : null; + } + + @Override + public boolean isSingular() { + return true; + } +} \ No newline at end of file diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties index cd315d7b667..7371678f50d 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties @@ -884,6 +884,9 @@ fcli.fod.issue.output.table.header.updateCount = Issues Updated fcli.fod.issue.output.table.header.skippedCount = Issues Skipped fcli.fod.issue.output.table.header.errorCount = Errors fcli.fod.issue.list.usage.header = List vulnerabilities. +fcli.fod.issue.get.usage.header = Get vulnerability details. +fcli.fod.issue.get.usage.description = Get detailed data for a single FoD vulnerability in a given release. +fcli.fod.issue.get.id = Vulnerability id or vulnId. fcli.fod.issue.list.usage.description = This command allows for listing FoD vulnerability data \ for a given application or release. By default, only visible issues will be returned; the --include option can \ be used to (also) include suppressed or fixed issues. If any such issues are included, the \ diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueCommands.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueCommands.java index a3d1b5e40bf..54b55eea3d4 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueCommands.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueCommands.java @@ -32,6 +32,7 @@ SSCIssueGroupGetCommand.class, SSCIssueGroupListCommand.class, SSCIssueCountCommand.class, + SSCIssueGetCommand.class, SSCIssueListCommand.class, SSCIssueUpdateCommand.class, } diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueGetCommand.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueGetCommand.java new file mode 100644 index 00000000000..a25e80b505b --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueGetCommand.java @@ -0,0 +1,56 @@ +/* + * 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.ssc.issue.cli.cmd; + +import com.fortify.cli.common.json.producer.IObjectNodeProducer; +import com.fortify.cli.common.json.producer.ObjectNodeProducerApplyFrom; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCOutputCommand; +import com.fortify.cli.ssc._common.rest.ssc.SSCUrls; +import com.fortify.cli.ssc.appversion.cli.mixin.SSCAppVersionResolverMixin; +import com.fortify.cli.ssc.issue.cli.mixin.SSCIssueBulkEmbedMixin; +import com.fortify.cli.ssc.issue.cli.mixin.SSCIssueIncludeMixin; + +import kong.unirest.HttpRequest; +import kong.unirest.UnirestInstance; +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Parameters; + +@Command(name = OutputHelperMixins.Get.CMD_NAME) +public class SSCIssueGetCommand extends AbstractSSCOutputCommand { + @Getter @Mixin private OutputHelperMixins.Get outputHelper; + @Mixin private SSCAppVersionResolverMixin.RequiredOption parentResolver; + @Parameters(index = "0", arity = "1", descriptionKey = "fcli.ssc.issue.get.id") + private String id; + @Mixin private SSCIssueBulkEmbedMixin bulkEmbedMixin; + @Mixin private SSCIssueIncludeMixin includeMixin; + + @Override + protected IObjectNodeProducer getObjectNodeProducer(UnirestInstance unirest) { + String appVersionId = parentResolver.getAppVersionId(unirest); + return requestObjectNodeProducerBuilder(ObjectNodeProducerApplyFrom.SPEC) + .baseRequest(getBaseRequest(unirest, appVersionId)) + .build(); + } + + private HttpRequest getBaseRequest(UnirestInstance unirest, String appVersionId) { + return unirest.get(SSCUrls.PROJECT_VERSION_ISSUE(appVersionId, id)).queryString("qm", "issues"); + } + + @Override + public boolean isSingular() { + return true; + } +} diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueIncludeMixin.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueIncludeMixin.java index 2a7a574c158..fcf0f497b2d 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueIncludeMixin.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueIncludeMixin.java @@ -31,7 +31,7 @@ public class SSCIssueIncludeMixin implements IHttpRequestUpdater, IRecordTransformer { @DisableTest(TestType.MULTI_OPT_PLURAL_NAME) - @Option(names = {"--include", "-i"}, split = ",", defaultValue = "visible", descriptionKey = "fcli.ssc.issue.list.includeIssue", paramLabel="") + @Option(names = {"--include", "-i"}, split = ",", defaultValue = "visible", descriptionKey = "fcli.ssc.issue.includeIssue", paramLabel="") private Set includes; public HttpRequest updateRequest(HttpRequest request) { diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties index 0c51be93a79..b14b8d4b415 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties @@ -504,13 +504,16 @@ fcli.ssc.issue.list.usage.description = This command allows for listing SSC vuln more immediate output. fcli.ssc.issue.list.output.table.header.visibilityMarker = fcli.ssc.issue.list.output.table.header.friority = Priority +fcli.ssc.issue.get.usage.header = Get application version vulnerability details. +fcli.ssc.issue.get.usage.description = Get detailed data for a single SSC vulnerability in a given application version. +fcli.ssc.issue.get.id = Issue id. fcli.ssc.issue.list.filter = Filter issues using the given (friendly or technical) filter. \ See 'fcli ssc issue list-filters' for allowed values. -fcli.ssc.issue.list.embed = Embed extra application version data. Allowed values: ${COMPLETION-CANDIDATES}. \ +fcli.ssc.issue.embed = Embed extra application version data. Allowed values: ${COMPLETION-CANDIDATES}. \ Using the --output option, this extra data can be included in the output. Using the --query option, \ this extra data can be queried upon. To get an understanding of the structure and contents of the \ embedded data, use the --output json or --output yaml options. -fcli.ssc.issue.list.includeIssue = By default, only visible issues will be returned. This option \ +fcli.ssc.issue.includeIssue = By default, only visible issues will be returned. This option \ accepts a comma-separated list to allow (also) removed, suppressed and/or hidden issues to be returned, \ for example `--include visible,removed` (to return both visible and removed issues) or `--include \ removed` (to return only removed issues). Allowed values: ${COMPLETION-CANDIDATES}. @@ -704,6 +707,9 @@ fcli.ssc.attribute.definition.output.table.args = id,category,guid,name,type,req fcli.ssc.aviator.output.table.args = id,application.name,name,artifactId fcli.ssc.custom-tag.output.table.args = guid,name,valueType fcli.ssc.issue.count.output.table.args = cleanName,totalCount,auditedCount +fcli.ssc.issue.get.output.table.args = id,visibilityMarker,friority,location,issueName +fcli.ssc.issue.get.output.table.header.visibilityMarker = +fcli.ssc.issue.get.output.table.header.friority = Priority fcli.ssc.issue.list.output.table.args = id,visibilityMarker,friority,location,issueName fcli.ssc.issue.filter-set.output.table.args = guid,title,defaultFilterSet fcli.ssc.issue.group.output.table.args = guid,displayName,entityType From 6ada3b4068b125d957c2b151b29ea56eceb56a30 Mon Sep 17 00:00:00 2001 From: mjain6 Date: Mon, 4 May 2026 16:44:54 +0530 Subject: [PATCH 2/2] Added changes as per review comments --- .../fod/issue/cli/cmd/FoDIssueGetCommand.java | 58 ++++++++++--------- .../cli/fod/issue/helper/FoDIssueHelper.java | 32 +++++----- .../cli/fod/i18n/FoDMessages.properties | 12 ++-- .../cli/ssc/i18n/SSCMessages.properties | 2 +- 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java index 7e10799e2f2..bf868cc6d63 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueGetCommand.java @@ -13,19 +13,22 @@ package com.fortify.cli.fod.issue.cli.cmd; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fortify.cli.common.exception.FcliSimpleException; +import com.fortify.cli.common.exception.FcliTechnicalException; import com.fortify.cli.common.json.producer.IObjectNodeProducer; import com.fortify.cli.common.json.producer.ObjectNodeProducerApplyFrom; import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; -import com.fortify.cli.common.util.DisableTest; -import com.fortify.cli.common.util.DisableTest.TestType; import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDOutputCommand; import com.fortify.cli.fod._common.rest.FoDUrls; import com.fortify.cli.fod._common.rest.helper.FoDInputTransformer; import com.fortify.cli.fod.issue.cli.mixin.FoDIssueEmbedMixin; import com.fortify.cli.fod.issue.cli.mixin.FoDIssueIncludeMixin; +import com.fortify.cli.fod.issue.helper.FoDIssueHelper; +import com.fortify.cli.fod.issue.helper.FoDIssueHelper.IssueAggregationData; import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin; +import com.fortify.cli.fod.release.helper.FoDReleaseDescriptor; import kong.unirest.HttpRequest; import kong.unirest.UnirestInstance; @@ -34,50 +37,49 @@ import picocli.CommandLine.Mixin; import picocli.CommandLine.Parameters; -@DisableTest(TestType.CMD_DEFAULT_TABLE_OPTIONS_PRESENT) @Command(name = OutputHelperMixins.Get.CMD_NAME) public class FoDIssueGetCommand extends AbstractFoDOutputCommand { @Getter @Mixin private OutputHelperMixins.Get outputHelper; @Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins @Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver; - @Parameters(index = "0", arity = "1", descriptionKey = "fcli.fod.issue.get.id") + @Parameters(index = "0", arity = "1", descriptionKey = "fcli.fod.issue.get.vulnId") private String vulnId; @Mixin private FoDIssueEmbedMixin embedMixin; @Mixin private FoDIssueIncludeMixin includeMixin; @Override protected IObjectNodeProducer getObjectNodeProducer(UnirestInstance unirest) { - String releaseId = releaseResolver.getReleaseId(unirest); - JsonNode issue = getIssue(unirest, releaseId); - return simpleObjectNodeProducerBuilder(ObjectNodeProducerApplyFrom.SPEC) - .source(issue) - .build(); - } - - private JsonNode getIssue(UnirestInstance unirest, String releaseId) { - boolean numericId = vulnId!=null && vulnId.chars().allMatch(Character::isDigit); - JsonNode issue = numericId - ? getIssueByFilter(unirest, releaseId, "id", vulnId) - : getIssueByFilter(unirest, releaseId, "vulnId", vulnId); + FoDReleaseDescriptor releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest); + String releaseId = releaseDescriptor.getReleaseId().toString(); + JsonNode issue = findIssue(unirest, releaseId); if ( issue==null ) { - issue = numericId - ? getIssueByFilter(unirest, releaseId, "vulnId", vulnId) - : getIssueByFilter(unirest, releaseId, "id", vulnId); + throw new FcliSimpleException(String.format("No vulnerability found for vulnId '%s' in release '%s'", vulnId, releaseDescriptor.getReleaseName())); } - if ( issue==null ) { - throw new FcliSimpleException(String.format("No issue found for id or vulnId '%s' in the specified release", vulnId)); + if ( issue instanceof ObjectNode issueObject ) { + issueObject.put("releaseId", releaseId); + issueObject.put("releaseName", releaseDescriptor.getReleaseName()); + FoDIssueHelper.transformRecord(issueObject, IssueAggregationData.forSingleRelease(issueObject)); } - return issue; + return simpleObjectNodeProducerBuilder(ObjectNodeProducerApplyFrom.SPEC) + .source(issue) + .build(); } - private JsonNode getIssueByFilter(UnirestInstance unirest, String releaseId, String fieldName, String value) { + private JsonNode findIssue(UnirestInstance unirest, String releaseId) { HttpRequest request = unirest.get(FoDUrls.VULNERABILITIES) .routeParam("relId", releaseId) - .queryString("filters", fieldName+":"+value) - .queryString("limit", "1"); - JsonNode body = includeMixin.updateRequest(request).asObject(JsonNode.class).getBody(); - JsonNode items = FoDInputTransformer.getItems(body); - return items!=null && items.isArray() && !items.isEmpty() ? items.get(0) : null; + .queryString("filters", "vulnId:" + vulnId) + .queryString("limit", "2"); + var response = includeMixin.updateRequest(request).asObject(JsonNode.class); + if ( response.getStatus() >= 400 ) { + throw new FcliTechnicalException(String.format("FoD API returned HTTP %d while searching for vulnerability '%s'", response.getStatus(), vulnId)); + } + JsonNode items = FoDInputTransformer.getItems(response.getBody()); + if ( items==null || !items.isArray() ) { return null; } + if ( items.size()>1 ) { + throw new FcliSimpleException(String.format("Multiple vulnerabilities found for vulnId '%s'; please check your input", vulnId)); + } + return items.isEmpty() ? null : items.get(0); } @Override diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/helper/FoDIssueHelper.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/helper/FoDIssueHelper.java index e6e4a906f8b..9749832dcf4 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/helper/FoDIssueHelper.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/helper/FoDIssueHelper.java @@ -152,7 +152,7 @@ public static IssueAggregationData forSingleRelease(ObjectNode issue) { .releaseNames(Set.of(releaseName)) .releaseIds(Set.of(releaseId)) .ids(Set.of(id)) - .vulnIds(Set.of(vulnId)) + .vulnIds(vulnId!=null ? Set.of(vulnId) : Collections.emptySet()) .build(); } @@ -179,7 +179,7 @@ public String getReleaseIdsString() { } public String getIdsString() { - return asString(ids); + return asString(ids); } private String asString(Set values) { @@ -192,25 +192,29 @@ private String asString(Set values) { /** Overload adding aggregation fields to an ObjectNode using provided data. */ public static final ObjectNode transformRecord(ObjectNode record, IssueAggregationData data) { transformRecord(record); // apply generic transformations first (rename etc.) - ArrayNode vulnIdsArray = JsonHelper.getObjectMapper().createArrayNode(); - data.getVulnIds().forEach(vulnIdsArray::add); - ArrayNode releaseNamesArray = JsonHelper.getObjectMapper().createArrayNode(); - data.getReleaseNames().forEach(releaseNamesArray::add); - ArrayNode releaseIdsArray = JsonHelper.getObjectMapper().createArrayNode(); - data.getReleaseIds().forEach(releaseIdsArray::add); - ArrayNode idsArray = JsonHelper.getObjectMapper().createArrayNode(); - data.getIds().forEach(idsArray::add); - record.set("vulnIds", vulnIdsArray); + record.set("vulnIds", toJsonNode(data.getVulnIds())); record.put("vulnIdsString", data.getVulnIdsString()); - record.set("foundInReleases", releaseNamesArray); + record.set("foundInReleases", toJsonNode(data.getReleaseNames())); record.put("foundInReleasesString", data.getReleaseNamesString()); - record.set("foundInReleaseIds", releaseIdsArray); + record.set("foundInReleaseIds", toJsonNode(data.getReleaseIds())); record.put("foundInReleaseIdsString", data.getReleaseIdsString()); - record.set("ids", idsArray); + record.set("ids", toJsonNode(data.getIds())); record.put("idsString", data.getIdsString()); return record; } + private static JsonNode toJsonNode(Set values) { + if ( values == null || values.isEmpty() ) { + return JsonHelper.getObjectMapper().getNodeFactory().textNode("N/A"); + } else if ( values.size() == 1 ) { + return JsonHelper.getObjectMapper().getNodeFactory().textNode(values.iterator().next()); + } else { + var array = JsonHelper.getObjectMapper().createArrayNode(); + values.forEach(array::add); + return array; + } + } + public static final FoDBulkIssueUpdateResponse updateIssues(UnirestInstance unirest, String releaseId, FoDBulkIssueUpdateRequest issueUpdateRequest) { ObjectNode body = objectMapper.valueToTree(issueUpdateRequest); var result = unirest.post(FoDUrls.VULNERABILITIES + "/bulk-edit") diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties index 7371678f50d..9885e616d6a 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties @@ -886,7 +886,7 @@ fcli.fod.issue.output.table.header.errorCount = Errors fcli.fod.issue.list.usage.header = List vulnerabilities. fcli.fod.issue.get.usage.header = Get vulnerability details. fcli.fod.issue.get.usage.description = Get detailed data for a single FoD vulnerability in a given release. -fcli.fod.issue.get.id = Vulnerability id or vulnId. +fcli.fod.issue.get.vulnId = Issue vulnerability id. fcli.fod.issue.list.usage.description = This command allows for listing FoD vulnerability data \ for a given application or release. By default, only visible issues will be returned; the --include option can \ be used to (also) include suppressed or fixed issues. If any such issues are included, the \ @@ -906,9 +906,9 @@ fcli.fod.issue.list.usage.description = This command allows for listing FoD vuln recommended to use server-side filtering, via use of the --filters-param or --query options. \ For example, if you are only interested in issues with a specific severity, you \ can use a query like --filters-param "severityString:Critical" or --query "severityString='Critical'". -fcli.fod.issue.list.output.table.header.visibilityMarker = -fcli.fod.issue.list.output.table.header.foundInReleases = Releases -fcli.fod.issue.list.output.table.header.foundInReleasesString = Releases +fcli.fod.issue.output.table.header.visibilityMarker = +fcli.fod.issue.output.table.header.foundInReleases = Releases +fcli.fod.issue.output.table.header.foundInReleasesString = Releases fcli.fod.issue.embed = Embed extra issue data. Due to FoD rate limits, this may significantly \ affect performance. Allowed values: ${COMPLETION-CANDIDATES}. \ Using the --output option, this extra data can be included in the output. Using the --query option, \ @@ -921,8 +921,7 @@ fcli.fod.issue.list.includeIssue = By default, only visible issues will be retur fcli.fod.issue.list.aggregate = Include aggregation data. fcli.fod.issue.update.usage.header = Bulk update vulnerabilities. fcli.fod.issue.update.usage.description = This command allows for updating the audit information \ - for multiple vulnerabilities. Note: for "vuln-ids" you can use either the numeric Id as shown in the FOD UI, \ - or the "vulnId" UUID field that is retrieved using the `fcli fod issue ls` command. + for multiple vulnerabilities. Note: for "vuln-ids" use the vulnId as shown in the FOD UI. fcli.fod.issue.update.user = The username or user id of the user the update will be recorded as. fcli.fod.issue.update.dev-status = The Developer Status to set for the vulnerabilities, see the FoD UI for valid values. fcli.fod.issue.update.auditor-status = The Auditor Status to set for the vulnerabilities, see the FoD UI for valid values. @@ -1060,6 +1059,7 @@ fcli.fod.rest.lookup.output.table.args = group,text,value fcli.fod.report.output.table.args = reportId,reportName,reportStatusType,reportType fcli.fod.report.report-template.output.table.args = value,text,group fcli.fod.issue.list.output.table.args = instanceId,visibilityMarker,severityString,category,location,foundInReleasesString +fcli.fod.issue.get.output.table.args = instanceId,visibilityMarker,severityString,category,location,foundInReleasesString fcli.fod.issue.update.output.table.args = totalCount,updateCount,skippedCount,errorCount fcli.fod.attribute.output.table.args = id,name,attributeType,attributeDataType,isRequired,isRestricted fcli.fod.aviator.apply-remediations.output.table.args = releaseId,totalRemediation,appliedRemediation,skippedRemediation,__action__ diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties index b14b8d4b415..5ee920ea8d9 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties @@ -504,7 +504,7 @@ fcli.ssc.issue.list.usage.description = This command allows for listing SSC vuln more immediate output. fcli.ssc.issue.list.output.table.header.visibilityMarker = fcli.ssc.issue.list.output.table.header.friority = Priority -fcli.ssc.issue.get.usage.header = Get application version vulnerability details. +fcli.ssc.issue.get.usage.header = Get vulnerability details. fcli.ssc.issue.get.usage.description = Get detailed data for a single SSC vulnerability in a given application version. fcli.ssc.issue.get.id = Issue id. fcli.ssc.issue.list.filter = Filter issues using the given (friendly or technical) filter. \