Skip to content

Commit

Permalink
chore: Updates/improvements for issue list commands
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Apr 3, 2024
1 parent 2b97d5c commit f9df61f
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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<IFoDEntityEmbedder> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -33,6 +34,7 @@ public class FoDIssueListCommand extends AbstractFoDBaseRequestOutputCommand imp
@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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IFoDEntityEmbedder> 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);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,8 @@ fcli.fod.oss-scan.download-latest.file = File path and name where to save the SB
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.list.embed = Embed extra issue data. Allowed values: ${COMPLETION-CANDIDATES}. \
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
package com.fortify.cli.ssc._common.rest;
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ public class SSCIssueListCommand extends AbstractSSCBaseRequestOutputCommand imp
@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);
@Mixin private SSCIssueBulkEmbedMixin bulkEmbedMixin;

@Override
public HttpRequest<?> getBaseRequest(UnirestInstance unirest) {
Expand Down

0 comments on commit f9df61f

Please sign in to comment.