diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/AbstractOutputHelperMixin.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/AbstractOutputHelperMixin.java index 2a6c720566..2a50641e5b 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/AbstractOutputHelperMixin.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/AbstractOutputHelperMixin.java @@ -162,6 +162,9 @@ protected final UnirestInstance getUnirestInstance() { * @param cmd */ protected final void addRecordTransformersForCommand(StandardOutputConfig standardOutputConfig, Object cmd) { + for ( var mixin : commandHelper.getCommandSpec().mixins().values() ) { + addRecordTransformersFromObject(standardOutputConfig, mixin.userObject()); + } addRecordTransformersFromObject(standardOutputConfig, getProductHelper()); addRecordTransformersFromObject(standardOutputConfig, cmd); addCommandActionResultRecordTransformer(standardOutputConfig, cmd); diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/cli/mixin/AbstractFoDEmbedMixin.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/cli/mixin/AbstractFoDEmbedMixin.java new file mode 100644 index 0000000000..1c40a42a86 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/cli/mixin/AbstractFoDEmbedMixin.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright 2021, 2023 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._common.cli.mixin; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.cli.mixin.CommandHelperMixin; +import com.fortify.cli.common.output.transform.IRecordTransformer; +import com.fortify.cli.common.rest.unirest.IUnirestInstanceSupplier; +import com.fortify.cli.fod._common.rest.embed.FoDEmbedder; +import com.fortify.cli.fod._common.rest.embed.IFoDEntityEmbedderSupplier; + +import kong.unirest.UnirestInstance; +import picocli.CommandLine.Mixin; + +public abstract class AbstractFoDEmbedMixin implements IRecordTransformer { + @Mixin private CommandHelperMixin commandHelper; + private FoDEmbedder embedder; + + @Override + public final JsonNode transformRecord(JsonNode record) { + if ( embedder==null ) { embedder = new FoDEmbedder(getEmbedSuppliers()); } + UnirestInstance unirest = commandHelper + .getCommandAs(IUnirestInstanceSupplier.class) + .orElseThrow().getUnirestInstance(); + embedder.transformRecord(unirest, record); + return record; + } + + protected abstract IFoDEntityEmbedderSupplier[] getEmbedSuppliers(); +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/FoDEmbedder.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/FoDEmbedder.java new file mode 100644 index 0000000000..231a68e7cc --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/FoDEmbedder.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright 2021, 2023 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._common.rest.embed; + +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import kong.unirest.UnirestInstance; + +/** + * This class takes zero or more {@link IFoDEntityEmbedderSupplier} instances + * as constructor argument(s), storing the {@link IFoDEntityEmbedder} instances + * generated by these suppliers, to provide the {@link #transformRecord(UnirestInstance, JsonNode)} + * method that embeds the requested data into a given record. + * + * @author rsenden + * + */ +public class FoDEmbedder { + private final Collection embedders; + + public FoDEmbedder(IFoDEntityEmbedderSupplier... suppliers) { + this.embedders = suppliers==null ? null : Stream.of(suppliers) + .map(IFoDEntityEmbedderSupplier::createEntityEmbedder) + .collect(Collectors.toList()); + } + + public JsonNode transformRecord(UnirestInstance unirest, JsonNode record) { + if ( embedders!=null ) { + if ( !(record instanceof ObjectNode) ) { + throw new RuntimeException("Can't embed data in records of type "+record.getNodeType()); + } + embedders.forEach(e->e.embed(unirest, (ObjectNode)record)); + } + return record; + } +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedder.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedder.java new file mode 100644 index 0000000000..c5a20f8edb --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedder.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright 2021, 2023 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._common.rest.embed; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import kong.unirest.UnirestInstance; + +/** + * Interface for executing one or more requests and adding the response data + * as embedded properties to a given record. + * + * @author rsenden + */ +@FunctionalInterface +public interface IFoDEntityEmbedder { + void embed(UnirestInstance unirest, ObjectNode record); +} \ No newline at end of file diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedderSupplier.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedderSupplier.java new file mode 100644 index 0000000000..a7302c00d9 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/rest/embed/IFoDEntityEmbedderSupplier.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright 2021, 2023 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._common.rest.embed; + +/** + * Interface for supplying an {@link IFoDEntityEmbedder} instance. + * @author rsenden + */ +@FunctionalInterface +public interface IFoDEntityEmbedderSupplier { + IFoDEntityEmbedder createEntityEmbedder(); +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_main/cli/cmd/FoDCommands.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_main/cli/cmd/FoDCommands.java index 3cbb6ede6c..59728d9256 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_main/cli/cmd/FoDCommands.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_main/cli/cmd/FoDCommands.java @@ -18,6 +18,7 @@ import com.fortify.cli.fod.action.cli.cmd.FoDActionCommands; import com.fortify.cli.fod.app.cli.cmd.FoDAppCommands; import com.fortify.cli.fod.dast_scan.cli.cmd.FoDDastScanCommands; +import com.fortify.cli.fod.issue.cli.cmd.FoDIssueCommands; import com.fortify.cli.fod.mast_scan.cli.cmd.FoDMastScanCommands; import com.fortify.cli.fod.microservice.cli.cmd.FoDMicroserviceCommands; import com.fortify.cli.fod.oss_scan.cli.cmd.FoDOssScanCommands; @@ -52,6 +53,7 @@ FoDDastScanCommands.class, FoDMastScanCommands.class, FoDOssScanCommands.class, + FoDIssueCommands.class, FoDReportCommands.class, FoDRestCommands.class, 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 new file mode 100644 index 0000000000..df459bc3ae --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueCommands.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.fortify.cli.common.cli.cmd.AbstractContainerCommand; + +import picocli.CommandLine; + +@CommandLine.Command(name = "issue", + subcommands = { + FoDIssueListCommand.class, + } +) +//@DefaultVariablePropertyName("applicationId") +public class FoDIssueCommands extends AbstractContainerCommand { +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueListCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueListCommand.java new file mode 100644 index 0000000000..cd7fa22256 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/cmd/FoDIssueListCommand.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.rest.query.IServerSideQueryParamGeneratorSupplier; +import com.fortify.cli.common.rest.query.IServerSideQueryParamValueGenerator; +import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; +import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDBaseRequestOutputCommand; +import com.fortify.cli.fod._common.rest.query.FoDFiltersParamGenerator; +import com.fortify.cli.fod._common.rest.query.cli.mixin.FoDFiltersParamMixin; +import com.fortify.cli.fod.issue.cli.mixin.FoDIssueEmbedMixin; +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; + +@Command(name = OutputHelperMixins.List.CMD_NAME) +public class FoDIssueListCommand extends AbstractFoDBaseRequestOutputCommand implements IServerSideQueryParamGeneratorSupplier { + @Getter @Mixin private OutputHelperMixins.List outputHelper; + @Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins + @Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver; + @Mixin private FoDFiltersParamMixin filterParamMixin; + @Mixin private FoDIssueEmbedMixin embedMixin; + @Getter private IServerSideQueryParamValueGenerator serverSideQueryParamGenerator = new FoDFiltersParamGenerator(); + // .add("id","applicationId") + // .add("name","applicationName") + // .add("criticality", "businessCriticalityType") + // .add("type", "applicationType"); + + @Override + public HttpRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities") + .routeParam("releaseId", releaseResolver.getReleaseId(unirest)); + } + + @Override + public boolean isSingular() { + return false; + } +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/mixin/FoDIssueEmbedMixin.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/mixin/FoDIssueEmbedMixin.java new file mode 100644 index 0000000000..d40169eb7d --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/issue/cli/mixin/FoDIssueEmbedMixin.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.mixin; + +import java.util.function.Supplier; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fortify.cli.common.util.DisableTest; +import com.fortify.cli.common.util.DisableTest.TestType; +import com.fortify.cli.fod._common.cli.mixin.AbstractFoDEmbedMixin; +import com.fortify.cli.fod._common.rest.embed.IFoDEntityEmbedder; +import com.fortify.cli.fod._common.rest.embed.IFoDEntityEmbedderSupplier; + +import kong.unirest.GetRequest; +import kong.unirest.UnirestInstance; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import picocli.CommandLine.Option; + +public class FoDIssueEmbedMixin extends AbstractFoDEmbedMixin { + @DisableTest(TestType.MULTI_OPT_PLURAL_NAME) + @Option(names = "--embed", required = false, split = ",", descriptionKey = "fcli.fod.issue.embed" ) + @Getter private FoDIssueEmbedderSupplier[] embedSuppliers; + + @RequiredArgsConstructor + public static enum FoDIssueEmbedderSupplier implements IFoDEntityEmbedderSupplier { + allData(FoDIssueAllDataEmbedder::new), + summary(FoDIssueSummaryEmbedder::new), + details(FoDIssueDetailsEmbedder::new), + recommendations(FoDIssueRecommendationsEmbedder::new), + history(FoDIssueHistoryEmbedder::new), + requestResponse(FoDIssueRequestResponseEmbedder::new), + headers(FoDIssueHeadersEmbedder::new), + parameters(FoDIssueParametersEmbedder::new), + traces(FoDIssueTracesEmbedder::new), + ; + + private final Supplier supplier; + + public IFoDEntityEmbedder createEntityEmbedder() { + return supplier.get(); + } + + private static abstract class AbstractFoDIssueEmbedder implements IFoDEntityEmbedder { + @Override + public void embed(UnirestInstance unirest, ObjectNode record) { + var releaseId = record.get("releaseId").asText(); + var vulnId = record.get("vulnId").asText(); + JsonNode response = getBaseRequest(unirest) + .routeParam("releaseId", releaseId) + .routeParam("vulnId", vulnId) + .queryString("limit", "-1") + .asObject(JsonNode.class) + .getBody(); + process(record, response); + } + + protected abstract GetRequest getBaseRequest(UnirestInstance unirest); + protected abstract void process(ObjectNode record, JsonNode response); + } + + private static final class FoDIssueAllDataEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/all-data"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("allData", response); + } + } + + private static final class FoDIssueSummaryEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/summary"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("summary", response); + } + } + + private static final class FoDIssueDetailsEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/details"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("details", response); + } + } + + private static final class FoDIssueRecommendationsEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/recommendations"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("recommendations", response); + } + } + + private static final class FoDIssueHistoryEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/history"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("history", response); + } + } + + private static final class FoDIssueRequestResponseEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/request-response"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("requestResponse", response); + } + } + + private static final class FoDIssueHeadersEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/headers"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("headers", response); + } + } + + private static final class FoDIssueParametersEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/parameters"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("parameters", response); + } + } + + private static final class FoDIssueTracesEmbedder extends AbstractFoDIssueEmbedder { + @Override + protected GetRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v3/releases/{releaseId}/vulnerabilities/{vulnId}/traces"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("traces", response); + } + } + + } +} 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 bc3638eae4..07022ebc13 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 @@ -705,6 +705,16 @@ fcli.fod.oss-scan.download.file = File path and name where to save the SBOM file fcli.fod.oss-scan.download-latest.usage.header = Download latest scan results from release. fcli.fod.oss-scan.download-latest.file = File path and name where to save the SBOM file. +# fcli fod issue +fcli.fod.issue.usage.header = Manage FoD issues (vulnerabilities) and related entities. +fcli.fod.issue.list.usage.header = List vulnerabilities. +# TODO --embed option yet to be added. +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, \ + 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 fod report fcli.fod.report.usage.header = Manage FoD reports. fcli.fod.report.output.header.reportId = Id @@ -783,3 +793,4 @@ fcli.fod.session.output.table.options = name,type,url,created,expires,expired fcli.fod.rest.lookup.output.table.options = group,text,value fcli.fod.report.output.table.options = reportId,reportName,reportStatusType,reportType fcli.fod.report.report-template.output.table.options = value,text,group +fcli.fod.issue.list.output.table.options = id,primaryLocation,lineNumber,category diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/bulk/package-info.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/package-info.java similarity index 94% rename from fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/bulk/package-info.java rename to fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/package-info.java index e043a03af7..f298092772 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/bulk/package-info.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/package-info.java @@ -13,4 +13,4 @@ /** * This package contains generic helper classes for working with the SSC REST API. */ -package com.fortify.cli.ssc._common.rest.bulk; \ No newline at end of file +package com.fortify.cli.ssc._common.rest; \ No newline at end of file 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 ef5d2dc086..ef6bc63dca 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, + SSCIssueListCommand.class, } ) public class SSCIssueCommands extends AbstractContainerCommand { diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueListCommand.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueListCommand.java new file mode 100644 index 0000000000..1aa690e062 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/cmd/SSCIssueListCommand.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.rest.query.IServerSideQueryParamGeneratorSupplier; +import com.fortify.cli.common.rest.query.IServerSideQueryParamValueGenerator; +import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCBaseRequestOutputCommand; +import com.fortify.cli.ssc._common.rest.query.SSCQParamGenerator; +import com.fortify.cli.ssc._common.rest.query.SSCQParamValueGenerators; +import com.fortify.cli.ssc._common.rest.query.cli.mixin.SSCQParamMixin; +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.SSCIssueFilterSetResolverMixin; +import com.fortify.cli.ssc.issue.helper.SSCIssueFilterHelper; +import com.fortify.cli.ssc.issue.helper.SSCIssueFilterSetDescriptor; + +import kong.unirest.GetRequest; +import kong.unirest.HttpRequest; +import kong.unirest.UnirestInstance; +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +@Command(name = OutputHelperMixins.List.CMD_NAME) +public class SSCIssueListCommand extends AbstractSSCBaseRequestOutputCommand implements IServerSideQueryParamGeneratorSupplier { + @Getter @Mixin private OutputHelperMixins.List outputHelper; + @Mixin private SSCAppVersionResolverMixin.RequiredOption parentResolver; + @Mixin private SSCIssueFilterSetResolverMixin.FilterSetOption filterSetResolver; + @Mixin private SSCQParamMixin qParamMixin; + @Mixin private SSCIssueBulkEmbedMixin bulkEmbedMixin; + @Option(names="--filter", required=false) private String filter; + + // For some reason, SSC q param doesn't use same property names as returned by SSC, + // so we list the proper mappings below. TODO Any other useful server-side queries? + @Getter private IServerSideQueryParamValueGenerator serverSideQueryParamGenerator = new SSCQParamGenerator() + .add("issueName", "category", SSCQParamValueGenerators::wrapInQuotes) + .add("fullFileName", "file", SSCQParamValueGenerators::wrapInQuotes); + + @Override + public HttpRequest getBaseRequest(UnirestInstance unirest) { + String appVersionId = parentResolver.getAppVersionId(unirest); + SSCIssueFilterSetDescriptor filterSetDescriptor = filterSetResolver.getFilterSetDescriptor(unirest, appVersionId); + GetRequest request = unirest.get("/api/v1/projectVersions/{id}/issues?limit=100&qm=issues") + .routeParam("id", appVersionId); + if ( filterSetDescriptor!=null ) { + request.queryString("filterset", filterSetDescriptor.getGuid()); + } + if ( filter!=null ) { + request.queryString("filter", new SSCIssueFilterHelper(unirest, appVersionId).getFilter(filter)); + } + return request; + } + + @Override + public boolean isSingular() { + return false; + } +} diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueBulkEmbedMixin.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueBulkEmbedMixin.java new file mode 100644 index 0000000000..ded6677bf8 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/cli/mixin/SSCIssueBulkEmbedMixin.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.mixin; + +import com.fortify.cli.common.util.DisableTest; +import com.fortify.cli.common.util.DisableTest.TestType; +import com.fortify.cli.ssc._common.output.cli.mixin.AbstractSSCBulkEmbedMixin; +import com.fortify.cli.ssc.issue.helper.SSCIssueEmbedderSupplier; + +import lombok.Getter; +import picocli.CommandLine.Option; + +public class SSCIssueBulkEmbedMixin extends AbstractSSCBulkEmbedMixin { + @DisableTest(TestType.MULTI_OPT_PLURAL_NAME) + @Option(names = "--embed", required = false, split = ",", descriptionKey = "fcli.ssc.issue.embed" ) + @Getter private SSCIssueEmbedderSupplier[] embedSuppliers; +} diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/helper/SSCIssueEmbedderSupplier.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/helper/SSCIssueEmbedderSupplier.java new file mode 100644 index 0000000000..4d10104ee2 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/issue/helper/SSCIssueEmbedderSupplier.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright 2021, 2023 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.helper; + +import java.util.function.Supplier; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fortify.cli.ssc._common.rest.bulk.ISSCEntityEmbedder; +import com.fortify.cli.ssc._common.rest.bulk.ISSCEntityEmbedderSupplier; +import com.fortify.cli.ssc._common.rest.bulk.SSCBulkRequestBuilder; +import com.fortify.cli.ssc._common.rest.helper.SSCInputTransformer; + +import kong.unirest.HttpRequest; +import kong.unirest.UnirestInstance; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SSCIssueEmbedderSupplier implements ISSCEntityEmbedderSupplier { + details(SSCIssueDetailsEmbedder::new), + comments(SSCIssueCommentsEmbedder::new), + auditHistory(SSCIssueAuditHistoryEmbedder::new), + ; + + private final Supplier supplier; + + public ISSCEntityEmbedder createEntityEmbedder() { + return supplier.get(); + } + + private static abstract class AbstractSSCIssueEmbedder implements ISSCEntityEmbedder { + @Override + public void addEmbedRequests(SSCBulkRequestBuilder builder, UnirestInstance unirest, JsonNode record) { + var id = record.get("id").asText(); + builder.request(getBaseRequest(unirest) + .routeParam("id", id) + .queryString("limit", "-1"), + response->process((ObjectNode)record, SSCInputTransformer.getDataOrSelf(response))); + } + + protected abstract HttpRequest getBaseRequest(UnirestInstance unirest); + protected abstract void process(ObjectNode record, JsonNode response); + } + + private static final class SSCIssueDetailsEmbedder extends AbstractSSCIssueEmbedder { + @Override + protected HttpRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v1/issueDetails/{id}"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("details", response); + } + } + + private static final class SSCIssueAuditHistoryEmbedder extends AbstractSSCIssueEmbedder { + @Override + protected HttpRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v1/issues/{id}/auditHistory?limit=-1"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("auditHistory", response); + } + } + + private static final class SSCIssueCommentsEmbedder extends AbstractSSCIssueEmbedder { + @Override + protected HttpRequest getBaseRequest(UnirestInstance unirest) { + return unirest.get("/api/v1/issues/{id}/comments?limit=-1"); + } + @Override + protected void process(ObjectNode record, JsonNode response) { + record.set("comments", response); + } + } +} 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 a625bb353f..166f7eb240 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 @@ -376,6 +376,13 @@ fcli.ssc.issue.count.by = Vulnerability grouping type. See 'fcli ssc issue list- allowed values. Default value: ${DEFAULT-VALUE}. fcli.ssc.issue.count.filter = Filter issue counts using the given (friendly or technical) filter. \ See 'fcli ssc issue list-filters' for allowed values. +fcli.ssc.issue.list.usage.header = List application version vulnerabilities. +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}. \ + 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.get-filter.usage.header = Get issue filter details. fcli.ssc.issue.filter = Technical or friendly filter as returned by the 'fcli ssc issue list-filters' command. fcli.ssc.issue.list-filters.usage.header = List application version issue filters. @@ -522,6 +529,7 @@ fcli.ssc.artifact.output.table.options = id,scanTypes,lastScanDate,uploadDate,st fcli.ssc.attribute.output.table.options = id,category,guid,name,valueString fcli.ssc.attribute.definition.output.table.options = id,category,guid,name,type,required fcli.ssc.issue.count.output.table.options = cleanName,totalCount,auditedCount +fcli.ssc.issue.list.output.table.options = id,primaryLocation,lineNumber,issueName fcli.ssc.issue.filter-set.output.table.options = guid,title,defaultFilterSet fcli.ssc.issue.group.output.table.options = guid,displayName,entityType fcli.ssc.issue.filter.output.table.options = entityType,friendlyFilter,technicalFilter