diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java index 40b071b8aa..7336dea391 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionDescriptor.java @@ -243,6 +243,8 @@ public static final class ActionStepDescriptor implements IActionIfSupplier { private List set; /** Optional write operations */ private List write; + /** Optional forEach operation */ + private ActionStepForEachDescriptor forEach; /** * This method is invoked by the parent element (which may either be another @@ -253,38 +255,7 @@ public final void postLoad(ActionDescriptor action) { if ( requests!=null ) { requests.forEach(d->d.postLoad(action)); } if ( set!=null ) { set.forEach(d->d.postLoad(action)); } if ( write!=null ) { write.forEach(d->d.postLoad(action)); } - } - } - - /** - * This class describes a forEach element, allowing iteration over the output of - * the parent element, like the response of a REST request or the contents of a - * action parameter. - */ - @Reflectable @NoArgsConstructor - @Data - public static final class ActionStepForEachDescriptor implements IActionIfSupplier { - /** Optional if-expression, executing steps only if condition evaluates to true */ - @JsonProperty("if") private SimpleExpression _if; - /** Optional break-expression, terminating forEach if condition evaluates to true */ - private SimpleExpression breakIf; - /** Required name for this step element */ - private String name; - /** Optional requests for which to embed the response in each forEach node */ - private List embed; - /** Steps to be repeated for each value */ - @JsonProperty("do") private List _do; - - /** - * This method is invoked by the {@link ActionStepDescriptor#postLoad()} - * method. It checks that required properties are set, then calls the postLoad() method for - * each sub-step. - */ - public final void postLoad(ActionDescriptor action) { - checkNotBlank("forEach name", name, this); - checkNotNull("forEach do", _do, this); - if ( embed!=null ) { embed.forEach(d->d.postLoad(action)); } - _do.forEach(d->d.postLoad(action)); + if ( forEach!=null ) { forEach.postLoad(action); } } } @@ -316,14 +287,12 @@ public void postLoad(ActionDescriptor action) { ()->"No value template found with name "+valueTemplate); } } - } - - // Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes - // issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11 - // TODO: Add other operations like 'merge' for merging two ObjectNodes or ArrayNodes? - @Reflectable - public static enum ActionStepSetOperation { - replace, append; + + // TODO: Add other operations like 'merge' for merging two ObjectNodes or ArrayNodes? + @Reflectable + public static enum ActionStepSetOperation { + replace, append; + } } /** @@ -348,6 +317,45 @@ public void postLoad(ActionDescriptor action) { } } + /** + * This class describes a forEach element, allowing iteration over the output of + * a given input. + */ + @Reflectable @NoArgsConstructor + @Data + public static final class ActionStepForEachDescriptor implements IActionIfSupplier { + /** Processor that runs the forEach steps. This expression must evaluate to an + * IActionStepForEachProcessor instance. */ + private SimpleExpression processor; + /** Optional if-expression, executing steps only if condition evaluates to true */ + @JsonProperty("if") private SimpleExpression _if; + /** Optional break-expression, terminating forEach if condition evaluates to true */ + private SimpleExpression breakIf; + /** Required name for this step element */ + private String name; + /** Steps to be repeated for each value */ + @JsonProperty("do") private List _do; + + /** + * This method is invoked by the {@link ActionStepDescriptor#postLoad()} + * method. It checks that required properties are set, then calls the postLoad() method for + * each sub-step. + */ + public final void postLoad(ActionDescriptor action) { + checkNotBlank("forEach name", name, this); + checkNotNull("forEach do", _do, this); + _do.forEach(d->d.postLoad(action)); + } + + @FunctionalInterface + public static interface IActionStepForEachProcessor { + /** Implementations of this method should invoke the given function for every + * JsonNode to be processed, and terminate processing if the given function + * returns false. */ + public void process(Function consumer); + } + } + /** * This class describes a REST request. */ @@ -377,7 +385,7 @@ public static final class ActionStepRequestDescriptor implements IActionIfSuppli /** Optional steps to be executed on request failure; if not declared, an exception will be thrown */ private List onFail; /** Optional forEach block to be repeated for every response element */ - private ActionStepForEachDescriptor forEach; + private ActionStepRequestForEachDescriptor forEach; /** * This method is invoked by {@link ActionStepDescriptor#postLoad()} @@ -397,23 +405,51 @@ protected final void postLoad(ActionDescriptor action) { forEach.postLoad(action); } } - } - - // Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes - // issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11 - @Reflectable - public static enum ActionStepRequestType { - simple, paged - } - - // Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes - // issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11 - @Reflectable @NoArgsConstructor - @Data - public static final class ActionStepRequestPagingProgressDescriptor { - private TemplateExpression prePageLoad; - private TemplateExpression postPageLoad; - private TemplateExpression postPageProcess; + + /** + * This class describes a request forEach element, allowing iteration over the output of + * the parent element, like the response of a REST request or the contents of a + * action parameter. + */ + @Reflectable @NoArgsConstructor + @Data + public static final class ActionStepRequestForEachDescriptor implements IActionIfSupplier { + /** Optional if-expression, executing steps only if condition evaluates to true */ + @JsonProperty("if") private SimpleExpression _if; + /** Optional break-expression, terminating forEach if condition evaluates to true */ + private SimpleExpression breakIf; + /** Required name for this step element */ + private String name; + /** Optional requests for which to embed the response in each forEach node */ + private List embed; + /** Steps to be repeated for each value */ + @JsonProperty("do") private List _do; + + /** + * This method is invoked by the {@link ActionStepDescriptor#postLoad()} + * method. It checks that required properties are set, then calls the postLoad() method for + * each sub-step. + */ + public final void postLoad(ActionDescriptor action) { + checkNotBlank("forEach name", name, this); + checkNotNull("forEach do", _do, this); + if ( embed!=null ) { embed.forEach(d->d.postLoad(action)); } + _do.forEach(d->d.postLoad(action)); + } + } + + @Reflectable + public static enum ActionStepRequestType { + simple, paged + } + + @Reflectable @NoArgsConstructor + @Data + public static final class ActionStepRequestPagingProgressDescriptor { + private TemplateExpression prePageLoad; + private TemplateExpression postPageLoad; + private TemplateExpression postPageProcess; + } } /** diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java index 0154e15db2..052fb2dd2a 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionRunner.java @@ -48,11 +48,13 @@ import com.fortify.cli.common.action.helper.ActionDescriptor.ActionRequestTargetDescriptor; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepDescriptor; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepForEachDescriptor; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepForEachDescriptor.IActionStepForEachProcessor; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor; -import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestPagingProgressDescriptor; -import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestType; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestForEachDescriptor; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestPagingProgressDescriptor; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestType; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetDescriptor; -import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetOperation; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetDescriptor.ActionStepSetOperation; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepWriteDescriptor; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionValidationException; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionValueTemplateDescriptor; @@ -155,7 +157,7 @@ public final ActionRunner addRequestHelper(String name, IActionRequestHelper req return this; } - private IActionRequestHelper getRequestHelper(String name) { + public IActionRequestHelper getRequestHelper(String name) { if ( StringUtils.isBlank(name) ) { if ( requestHelpers.size()==1 ) { return requestHelpers.values().iterator().next(); @@ -307,6 +309,7 @@ private final void processStep(ActionStepDescriptor step) { processSupplier(step::getDebug, this::processDebugStep); processSupplier(step::get_throw, this::processThrowStep); processSupplier(step::getRequests, this::processRequestsStep); + processSupplier(step::getForEach, this::processForEachStep); processAll(step::getSet, this::processSetStep); processAll(step::getWrite, this::processWriteStep); } @@ -438,6 +441,19 @@ private void processThrowStep(TemplateExpression message) { throw new RuntimeException(spelEvaluator.evaluate(message, localData, String.class)); } + private void processForEachStep(ActionStepForEachDescriptor forEach) { + spelEvaluator.evaluate(forEach.getProcessor(), localData, IActionStepForEachProcessor.class) + .process(node->processForEachStepNode(forEach, node)); + } + + private boolean processForEachStepNode(ActionStepForEachDescriptor forEach, JsonNode node) { + setDataValue(forEach.getName(), node); + if ( _if(forEach) ) { + processSteps(forEach.get_do()); + } + return true; + } + private void processRequestsStep(List requests) { if ( requests!=null ) { var requestsProcessor = new ActionStepRequestsProcessor(); @@ -452,7 +468,7 @@ private final void processResponse(ActionStepRequestDescriptor requestDescriptor localData.set(name+"_raw", rawBody); localData.set(name, body); processOnResponse(requestDescriptor); - processForEach(requestDescriptor); + processRequestStepForEach(requestDescriptor); } private final void processFailure(ActionStepRequestDescriptor requestDescriptor, UnirestException e) { @@ -467,15 +483,15 @@ private final void processOnResponse(ActionStepRequestDescriptor requestDescript processSteps(onResponseSteps); } - private final void processForEach(ActionStepRequestDescriptor requestDescriptor) { + private final void processRequestStepForEach(ActionStepRequestDescriptor requestDescriptor) { var forEach = requestDescriptor.getForEach(); if ( forEach!=null ) { var input = localData.get(requestDescriptor.getName()); if ( input!=null ) { if ( input instanceof ArrayNode ) { - updateForEachTotalCount(forEach, (ArrayNode)input); - processForEachEmbed(forEach, (ArrayNode)input); - processForEach(forEach, (ArrayNode)input, this::processForEachEntryDo); + updateRequestStepForEachTotalCount(forEach, (ArrayNode)input); + processRequestStepForEachEmbed(forEach, (ArrayNode)input); + processRequestStepForEach(forEach, (ArrayNode)input, this::processRequestStepForEachEntryDo); } else { throw new ActionValidationException("forEach not supported on node type "+input.getNodeType()); } @@ -483,18 +499,18 @@ private final void processForEach(ActionStepRequestDescriptor requestDescriptor) } } - private final void processForEachEmbed(ActionStepForEachDescriptor forEach, ArrayNode source) { + private final void processRequestStepForEachEmbed(ActionStepRequestForEachDescriptor forEach, ArrayNode source) { var requestExecutor = new ActionStepRequestsProcessor(); - processForEach(forEach, source, getForEachEntryEmbedProcessor(requestExecutor)); + processRequestStepForEach(forEach, source, getRequestForEachEntryEmbedProcessor(requestExecutor)); requestExecutor.executeRequests(); } @FunctionalInterface - private interface IForEachEntryProcessor { - void process(ActionStepForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData); + private interface IRequestStepForEachEntryProcessor { + void process(ActionStepRequestForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData); } - private final void processForEach(ActionStepForEachDescriptor forEach, ArrayNode source, IForEachEntryProcessor entryProcessor) { + private final void processRequestStepForEach(ActionStepRequestForEachDescriptor forEach, ArrayNode source, IRequestStepForEachEntryProcessor entryProcessor) { for ( int i = 0 ; i < source.size(); i++ ) { var currentNode = source.get(i); var newData = JsonHelper.shallowCopy(localData); @@ -510,19 +526,19 @@ private final void processForEach(ActionStepForEachDescriptor forEach, ArrayNode } } - private void updateForEachTotalCount(ActionStepForEachDescriptor forEach, ArrayNode array) { + private void updateRequestStepForEachTotalCount(ActionStepRequestForEachDescriptor forEach, ArrayNode array) { var totalCountName = String.format("total%sCount", StringUtils.capitalize(forEach.getName())); var totalCount = localData.get(totalCountName); if ( totalCount==null ) { totalCount = new IntNode(0); } localData.put(totalCountName, totalCount.asInt()+array.size()); } - private void processForEachEntryDo(ActionStepForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData) { + private void processRequestStepForEachEntryDo(ActionStepRequestForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData) { var processor = new ActionStepsProcessor(newData, this); processor.processSteps(forEach.get_do()); } - private IForEachEntryProcessor getForEachEntryEmbedProcessor(ActionStepRequestsProcessor requestExecutor) { + private IRequestStepForEachEntryProcessor getRequestForEachEntryEmbedProcessor(ActionStepRequestsProcessor requestExecutor) { return (forEach, currentNode, newData) -> { if ( !currentNode.isObject() ) { // TODO Improve exception message? @@ -592,6 +608,7 @@ private void executeRequest(String target, List request } public static interface IActionRequestHelper extends AutoCloseable { + public UnirestInstance getUnirestInstance(); public JsonNode transformInput(JsonNode input); public void executePagedRequest(ActionRequestDescriptor requestDescriptor); public void executeSimpleRequests(List requestDescriptor); diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java index 7a35a93106..e2638fcf90 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionSpelFunctions.java @@ -23,6 +23,7 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.nodes.TextNode; import org.jsoup.safety.Safelist; import com.formkiq.graalvm.annotations.Reflectable; @@ -82,6 +83,29 @@ public static final String htmlToText(String html) { return Jsoup.clean(s, "", Safelist.none(), new Document.OutputSettings().prettyPrint(false)); } + public static final String cleanRuleDescription(String description) { + if( description==null ) { return ""; } + Document document = Jsoup.parse(description); + document.outputSettings(new Document.OutputSettings().prettyPrint(false));//makes html() preserve linebreaks and spacing + document.select("br").append("\\n"); + document.select("p").prepend("\\n\\n"); + var paragraphs = document.select("Paragraph"); + for ( var p : paragraphs ) { + var altParagraph = p.select("AltParagraph"); + if ( !altParagraph.isEmpty() ) { + p.replaceWith(new TextNode(String.join("\n\n",altParagraph.eachText()))); + } else { + p.remove(); + } + + } + document.select("IfDef").remove(); + document.select("ConditionalText").remove(); + + String s = document.html().replaceAll("\\\\n", "\n"); + return Jsoup.clean(s, "", Safelist.none(), new Document.OutputSettings().prettyPrint(false)); + } + /** * @param html to be converted to plain text * @return Single line of plain text for the given HTML contents diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml index 7a160b32e3..6f1ccc6b03 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml @@ -84,29 +84,19 @@ valueTemplates: - name: rules contents: - id: ${issue.id+''} + id: ${issue.checkId} shortDescription: text: ${issue.category} fullDescription: - text: ${#htmlToText(issue.details?.summary)} + text: ${#cleanRuleDescription(issue.details?.summary)} help: - text: | - ${#htmlToText(issue.details?.explanation)} - - ${#htmlToText(issue.recommendations?.recommendations)} - - - For more information, see ${#fod.issueBrowserUrl(issue)} - properties: - tags: ${{issue.severityString}} - precision: ${(issue.severityString matches "(Critical|Medium)") ? "high":"low" } - security-severity: ${{Critical:10.0,High:8.9,Medium:6.9,Low:3.9}.get(issue.severityString)+''} + text: ${#cleanRuleDescription(issue.details?.explanation)} - name: results contents: - ruleId: ${issue.id+''} + ruleId: ${issue.checkId} message: - text: ${#htmlToText(issue.details?.summary)} + text: ${#htmlToText(issue.details?.summary)}. For more information, see ${#fod.issueBrowserUrl(issue)} level: ${(issue.severityString matches "(Critical|High)") ? "warning":"note" } partialFingerprints: issueInstanceId: ${issue.instanceId} diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/transfer/SSCFileTransferHelper.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/transfer/SSCFileTransferHelper.java index 86d27797fb..4c04900c11 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/transfer/SSCFileTransferHelper.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/_common/rest/transfer/SSCFileTransferHelper.java @@ -119,7 +119,7 @@ public static enum SSCFileTransferTokenType { REPORT_FILE } - private static final class SSCFileTransferTokenSupplier implements AutoCloseable, Supplier { + public static final class SSCFileTransferTokenSupplier implements AutoCloseable, Supplier { private final UnirestInstance unirest; private final String token; diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionRunCommand.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionRunCommand.java index 54a78808b1..fcbcad9c5d 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionRunCommand.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionRunCommand.java @@ -12,7 +12,15 @@ *******************************************************************************/ package com.fortify.cli.ssc.action.cli.cmd; +import java.io.InputStream; import java.util.List; +import java.util.function.Function; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import org.springframework.expression.spel.support.SimpleEvaluationContext; @@ -20,23 +28,32 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.formkiq.graalvm.annotations.Reflectable; import com.fortify.cli.common.action.cli.cmd.AbstractActionRunCommand; +import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepForEachDescriptor.IActionStepForEachProcessor; import com.fortify.cli.common.action.helper.ActionDescriptor.ActionValidationException; import com.fortify.cli.common.action.helper.ActionRunner; import com.fortify.cli.common.action.helper.ActionRunner.IActionRequestHelper.BasicActionRequestHelper; import com.fortify.cli.common.action.helper.ActionRunner.ParameterTypeConverterArgs; +import com.fortify.cli.common.action.helper.ActionSpelFunctions; +import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.product.IProductHelper; import com.fortify.cli.common.rest.unirest.IUnirestInstanceSupplier; import com.fortify.cli.common.spring.expression.SpelHelper; import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.ssc._common.rest.SSCUrls; import com.fortify.cli.ssc._common.rest.bulk.SSCBulkRequestBuilder; import com.fortify.cli.ssc._common.rest.helper.SSCProductHelper; +import com.fortify.cli.ssc._common.rest.transfer.SSCFileTransferHelper.SSCFileTransferTokenSupplier; +import com.fortify.cli.ssc._common.rest.transfer.SSCFileTransferHelper.SSCFileTransferTokenType; import com.fortify.cli.ssc._common.session.cli.mixin.SSCUnirestInstanceSupplierMixin; import com.fortify.cli.ssc.appversion.helper.SSCAppVersionHelper; import com.fortify.cli.ssc.issue.helper.SSCIssueFilterSetHelper; import kong.unirest.HttpRequest; +import kong.unirest.RawResponse; +import kong.unirest.UnirestInstance; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -54,13 +71,17 @@ protected void configure(ActionRunner templateRunner, SimpleEvaluationContext co templateRunner .addParameterConverter("appversion_single", this::loadAppVersion) .addParameterConverter("filterset", this::loadFilterSet) - .addRequestHelper("ssc", new SSCDataExtractRequestHelper(unirestInstanceSupplier::getUnirestInstance, SSCProductHelper.INSTANCE)); + .addRequestHelper("ssc", new SSCActionRequestHelper(unirestInstanceSupplier::getUnirestInstance, SSCProductHelper.INSTANCE)); context.setVariable("ssc", new SSCSpelFunctions(templateRunner)); } @RequiredArgsConstructor @Reflectable public final class SSCSpelFunctions { private final ActionRunner templateRunner; + public IActionStepForEachProcessor ruleDescriptionsProcessor(String appVersionId) { + var unirest = templateRunner.getRequestHelper("ssc").getUnirestInstance(); + return new SSCFPRRuleDescriptionProcessor(unirest, appVersionId)::process; + } public String issueBrowserUrl(ObjectNode issue, ObjectNode filterset) { var deepLinkExpression = baseUrl() +"/html/ssc/version/${projectVersionId}/fix/${id}/?engineType=${engineType}&issue=${issueInstanceId}"; @@ -110,8 +131,8 @@ private final JsonNode loadFilterSet(String titleOrId, ParameterTypeConverterArg return filterSetDescriptor.asJsonNode(); } - private static final class SSCDataExtractRequestHelper extends BasicActionRequestHelper { - public SSCDataExtractRequestHelper(IUnirestInstanceSupplier unirestInstanceSupplier, IProductHelper productHelper) { + private static final class SSCActionRequestHelper extends BasicActionRequestHelper { + public SSCActionRequestHelper(IUnirestInstanceSupplier unirestInstanceSupplier, IProductHelper productHelper) { super(unirestInstanceSupplier, productHelper); } @@ -128,10 +149,89 @@ public void executeSimpleRequests(List requestDescripto } private HttpRequest createRequest(ActionRequestDescriptor requestDescriptor) { - var request = getUnirestInstance(). request(requestDescriptor.getMethod(), requestDescriptor.getUri()) + var request = getUnirestInstance().request(requestDescriptor.getMethod(), requestDescriptor.getUri()) .queryString(requestDescriptor.getQueryParams()); var body = requestDescriptor.getBody(); return body==null ? request : request.body(body); } } + + @RequiredArgsConstructor + private final class SSCFPRRuleDescriptionProcessor { + private final UnirestInstance unirest; + private final String appVersionId; + + @SneakyThrows + public final void process(Function consumer) { + try ( SSCFileTransferTokenSupplier tokenSupplier = new SSCFileTransferTokenSupplier(unirest, SSCFileTransferTokenType.DOWNLOAD); ) { + unirest.get(SSCUrls.DOWNLOAD_CURRENT_FPR(appVersionId, false)) + .routeParam("downloadToken", tokenSupplier.get()).asObject(r->processFpr(r, consumer)).getBody(); + } + } + + @SneakyThrows + private final String processFpr(RawResponse r, Function consumer) { + try ( var zis = new ZipInputStream(r.getContent()) ) { + ZipEntry entry; + while ( (entry = zis.getNextEntry())!=null ) { + if ( "audit.fvdl".equals(entry.getName()) ) { + processAuditFvdl(zis, consumer); break; + } + } + } + return null; + } + + private final void processAuditFvdl(InputStream is, Function consumer) throws XMLStreamException { + var factory = XMLInputFactory.newInstance(); + var reader = factory.createXMLStreamReader(is); + while(reader.hasNext()) { + int eventType = reader.next(); + if ( eventType==XMLStreamReader.START_ELEMENT && "Description".equals(reader.getLocalName()) ) { + if (!processDescription(reader, consumer)) { break; } + } + } + } + + private boolean processDescription(XMLStreamReader reader, Function consumer) throws XMLStreamException { + var ruleId = reader.getAttributeValue(null, "classID"); + String ruleAbstract = null; + String ruleExplanation = null; + int level = 1; + while(level > 0 && reader.hasNext()) { + int eventType = reader.next(); + switch ( eventType ) { + case XMLStreamReader.START_ELEMENT: + level++; + switch ( reader.getLocalName() ) { + case "Abstract": ruleAbstract = readString(reader); break; + case "Explanation": ruleExplanation = readString(reader); break; + } + case XMLStreamReader.END_ELEMENT: + level--; break; + } + } + var entry = JsonHelper.getObjectMapper().createObjectNode() + .put("id", ruleId) + .put("abstract", ActionSpelFunctions.cleanRuleDescription(ruleAbstract)) + .put("explanation", ActionSpelFunctions.cleanRuleDescription(ruleExplanation)); + return consumer.apply(entry); + } + + private String readString(XMLStreamReader reader) throws XMLStreamException { + StringBuilder result = new StringBuilder(); + while (reader.hasNext()) { + int eventType = reader.next(); + switch (eventType) { + case XMLStreamReader.CHARACTERS: + case XMLStreamReader.CDATA: + result.append(reader.getText()); + break; + case XMLStreamReader.END_ELEMENT: + return result.toString(); + } + } + throw new XMLStreamException("Premature end of file"); + } + } } diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml index 440b356ed1..49517c00f7 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml @@ -49,6 +49,14 @@ steps: - set: - name: lastStaticScan value: ${artifact._embed.scans?.^[type=='SCA']} + - progress: Processing rule data + - forEach: + processor: "#ssc.ruleDescriptionsProcessor(parameters.appversion.id)" + name: rule + do: + - set: + - name: rules + operation: append - progress: Processing issue data - requests: - name: issues @@ -68,8 +76,6 @@ steps: uri: /api/v1/issueDetails/${issue.id} do: - set: - - name: rules - operation: append - name: results operation: append - write: @@ -95,29 +101,19 @@ valueTemplates: - name: rules contents: - id: ${issue.id+''} - shortDescription: - text: ${issue.issueName} + id: ${rule.id} + #shortDescription: + # text: ${issue.issueName} fullDescription: - text: ${issue.details?.brief} + text: ${rule.abstract} help: - text: | - ${#htmlToText(issue.details?.detail)} - - ${#htmlToText(issue.details?.recommendation)} - - - For more information, see ${#ssc.issueBrowserUrl(issue,parameters.filterset)} - properties: - tags: ${{issue.friority}} - precision: ${(issue.friority matches "(Critical|Medium)") ? "high":"low" } - security-severity: ${{Critical:10.0,High:8.9,Medium:6.9,Low:3.9}.get(issue.friority)+''} + text: ${#htmlToText(rule.explanation)} - name: results contents: - ruleId: ${issue.id+''} + ruleId: ${issue.primaryRuleGuid} message: - text: ${issue.details?.brief} + text: ${issue.details?.brief}. For more information, see ${#ssc.issueBrowserUrl(issue,parameters.filterset)} level: ${(issue.friority matches "(Critical|High)") ? "warning":"note" } partialFingerprints: issueInstanceId: ${issue.issueInstanceId} diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/lsr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/lsr.yaml new file mode 100644 index 0000000000..30f3468460 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/lsr.yaml @@ -0,0 +1,26 @@ +usage: + header: Test + description: | + Test + +defaults: + requestTarget: ssc + +parameters: + - name: appversion + cliAliases: av + description: "Required application version id or :" + type: appversion_single + +steps: + - progress: Loading latest static scan + - forEach: + processor: "#ssc.ruleDescriptionsProcessor(parameters.appversion.id)" + name: rule + do: + - write: + - to: stdout + value: | + ${rule.id} + ${rule.abstract} + ${rule.explanation} \ No newline at end of file diff --git a/fcli-other/fcli-bom/build.gradle b/fcli-other/fcli-bom/build.gradle index 637babf84c..1ecd2d114e 100644 --- a/fcli-other/fcli-bom/build.gradle +++ b/fcli-other/fcli-bom/build.gradle @@ -19,7 +19,7 @@ dependencies { // Annotation-based reflect-config.json generation api('com.formkiq:graalvm-annotations:1.2.0') - api('com.formkiq:graalvm-annotations-processor:1.4.1') + api('com.formkiq:graalvm-annotations-processor:1.4.2') // ANSI support api("org.fusesource.jansi:jansi:2.4.1")