From 5a95b250385230640b85805373df2d942dc37ad1 Mon Sep 17 00:00:00 2001 From: Ruud Senden <8635138+rsenden@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:23:51 +0100 Subject: [PATCH] chore: Partial data-extract implementation --- .../output/standard/StandardOutputWriter.java | 9 +- .../cli/common/rest/paging/PagingHelper.java | 19 ++ .../common/spring/expression/SpelHelper.java | 14 + .../expression/wrapper/SimpleExpression.java | 47 +++ .../wrapper/SimpleExpressionDeserializer.java | 55 ++++ .../wrapper/TemplateExpression.java | 47 +++ .../TemplateExpressionDeserializer.java | 55 ++++ .../expression/wrapper/WrappedExpression.java | 292 ++++++++++++++++++ .../sc_sast/_main/cli/cmd/SCSastCommands.java | 2 + .../cmd/AbstractDataExtractCreateCommand.java | 129 ++++++++ ...stractDataExtractListTemplatesCommand.java | 32 ++ .../_common/helper/DataExtractRestHelper.java | 55 ++++ .../helper/DataExtractTemplateDescriptor.java | 266 ++++++++++++++++ .../helper/DataExtractTemplateExecutor.java | 230 ++++++++++++++ .../helper/DataExtractTemplateHelper.java | 86 ++++++ .../cli/cmd/SCSastDataExtractCommands.java | 26 ++ .../cmd/SCSastDataExtractCreateCommand.java | 89 ++++++ .../cli/data_extract/templates/SC-SAST.zip | Bin 200 -> 0 bytes .../templates/github-code-scanning.yaml | 24 ++ .../sc_sast/i18n/SCSastMessages.properties | 6 + 20 files changed, 1476 insertions(+), 7 deletions(-) create mode 100644 fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpression.java create mode 100644 fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpressionDeserializer.java create mode 100644 fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpression.java create mode 100644 fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpressionDeserializer.java create mode 100644 fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/WrappedExpression.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractCreateCommand.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractListTemplatesCommand.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractRestHelper.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateDescriptor.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateExecutor.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateHelper.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCommands.java create mode 100644 fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCreateCommand.java delete mode 100644 fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/data_extract/templates/SC-SAST.zip create mode 100644 fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/data_extract/templates/github-code-scanning.yaml diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/output/standard/StandardOutputWriter.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/output/standard/StandardOutputWriter.java index 247a4564c2..a1a8f7cde9 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/output/standard/StandardOutputWriter.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/output/standard/StandardOutputWriter.java @@ -168,13 +168,8 @@ private final void writeRecords(IRecordWriter recordWriter, HttpRequest httpR * @param httpRequest * @param nextPageRequestProducer */ - private final void writeRecords(IRecordWriter recordWriter, HttpRequest originalRequest, INextPageRequestProducer nextPageRequestProducer) { - var currentRequest = originalRequest; - while ( currentRequest!=null ) { - HttpResponse response = currentRequest.asObject(JsonNode.class); - writeRecords(recordWriter, response); - currentRequest = nextPageRequestProducer.getNextPageRequest(originalRequest, response); - } + private final void writeRecords(IRecordWriter recordWriter, HttpRequest initialRequest, INextPageRequestProducer nextPageRequestProducer) { + PagingHelper.processPages(initialRequest, nextPageRequestProducer, r->writeRecords(recordWriter, r)); } /** diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/paging/PagingHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/paging/PagingHelper.java index 4b47dda9ab..eb388f7a0b 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/paging/PagingHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/paging/PagingHelper.java @@ -12,6 +12,8 @@ *******************************************************************************/ package com.fortify.cli.common.rest.paging; +import java.util.function.Consumer; + import com.fasterxml.jackson.databind.JsonNode; import kong.unirest.Header; @@ -61,6 +63,23 @@ public static final INextPageRequestProducer asNextPageRequestProducer(UnirestIn return unirest==null || nextPageUrlProducer==null ? null : new NextPageRequestProducer(unirest, nextPageUrlProducer); } + public static final void processPages(UnirestInstance unirest, HttpRequest initialRequest, INextPageUrlProducer nextPageUrlProducer, Consumer> consumer) { + var nextPageRequestProducer = asNextPageRequestProducer(unirest, nextPageUrlProducer); + if ( nextPageRequestProducer==null ) { + throw new IllegalStateException("Cannot process pages without a valid NextPageRequestProducer"); + } + processPages(initialRequest, nextPageRequestProducer, consumer); + } + + public static final void processPages(HttpRequest initialRequest, INextPageRequestProducer nextPageRequestProducer, Consumer> consumer) { + var currentRequest = initialRequest; + while ( currentRequest!=null ) { + HttpResponse response = currentRequest.asObject(JsonNode.class); + consumer.accept(response); + currentRequest = nextPageRequestProducer.getNextPageRequest(initialRequest, response); + } + } + @RequiredArgsConstructor private static final class NextPageRequestProducer implements INextPageRequestProducer { private final UnirestInstance unirest; diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/SpelHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/SpelHelper.java index eb41c96e63..69ebbcaa05 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/SpelHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/SpelHelper.java @@ -14,9 +14,23 @@ import java.lang.reflect.Method; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.SimpleEvaluationContext; +import com.fortify.cli.common.spring.expression.wrapper.SimpleExpression; +import com.fortify.cli.common.spring.expression.wrapper.TemplateExpression; + public final class SpelHelper { + private static final SpelExpressionParser parser = new SpelExpressionParser(); + private static final TemplateParserContext templateContext = new TemplateParserContext("${","}"); + + public static final SimpleExpression parseSimpleExpression(String s) { + return new SimpleExpression(parser.parseExpression(s)); + } + public static final TemplateExpression parseTemplateExpression(String s) { + return new TemplateExpression(parser.parseExpression(s, templateContext)); + } public static final void registerFunctions(SimpleEvaluationContext context, Class clazz) { for ( Method m : clazz.getDeclaredMethods() ) { context.setVariable(m.getName(), m); diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpression.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpression.java new file mode 100644 index 0000000000..23625e90f1 --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpression.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * (c) Copyright 2020 Micro Focus or one of its affiliates, a Micro Focus company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + ******************************************************************************/ +package com.fortify.cli.common.spring.expression.wrapper; + +import org.springframework.expression.Expression; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + *

This is a simple wrapper class for a Spring {@link Expression} + * instance. It's main use is in combination with + * {@link SimpleExpressionDeserializer} to allow automatic + * conversion from String values to simple {@link Expression} + * instances in JSON/YAML documents.

+ * + *

The reason for needing this wrapper class is to differentiate + * with templated {@link Expression} instances that are handled + * by {@link TemplateExpressionDeserializer}.

+ */ +@JsonDeserialize(using = SimpleExpressionDeserializer.class) +public class SimpleExpression extends WrappedExpression { + public SimpleExpression(Expression target) { + super(target); + } +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpressionDeserializer.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpressionDeserializer.java new file mode 100644 index 0000000000..790d39d63f --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/SimpleExpressionDeserializer.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * (c) Copyright 2020 Micro Focus or one of its affiliates, a Micro Focus company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + ******************************************************************************/ +package com.fortify.cli.common.spring.expression.wrapper; + +import java.io.IOException; + +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +/** + * This Jackson deserializer allows parsing String values into an + * SpEL Expression object. + */ +@Component +public final class SimpleExpressionDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + private static final SpelExpressionParser parser = new SpelExpressionParser(); + public SimpleExpressionDeserializer() { this(null); } + public SimpleExpressionDeserializer(Class vc) { super(vc); } + + @Override + public SimpleExpression deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + return node==null || node.isNull() ? null : new SimpleExpression(parser.parseExpression(node.asText())); + } +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpression.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpression.java new file mode 100644 index 0000000000..ec37450f8d --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpression.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * (c) Copyright 2020 Micro Focus or one of its affiliates, a Micro Focus company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + ******************************************************************************/ +package com.fortify.cli.common.spring.expression.wrapper; + +import org.springframework.expression.Expression; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + *

This is a simple wrapper class for a Spring {@link Expression} + * instance. It's main use is in combination with + * {@link TemplateExpressionDeserializer} to allow automatic + * conversion from String values to templated {@link Expression} + * instances.

+ * + *

The reason for needing this wrapper class is to differentiate + * with non-templated {@link Expression} instances that are + * handled by {@link SimpleExpressionDeserializer}.

+ */ +@JsonDeserialize(using = TemplateExpressionDeserializer.class) +public class TemplateExpression extends WrappedExpression { + public TemplateExpression(Expression target) { + super(target); + } +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpressionDeserializer.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpressionDeserializer.java new file mode 100644 index 0000000000..ca577c1564 --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/TemplateExpressionDeserializer.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * (c) Copyright 2020 Micro Focus or one of its affiliates, a Micro Focus company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + ******************************************************************************/ +package com.fortify.cli.common.spring.expression.wrapper; + +import java.io.IOException; + +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fortify.cli.common.spring.expression.SpelHelper; + +/** + * This {@link PropertyEditor} allows parsing String values into a + * TemplateExpression object. + */ +@Component +public final class TemplateExpressionDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + public TemplateExpressionDeserializer() { this(null); } + public TemplateExpressionDeserializer(Class vc) { super(vc); } + + @Override + public TemplateExpression deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + return node==null || node.isNull() ? null : SpelHelper.parseTemplateExpression(node.asText()); + } +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/WrappedExpression.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/WrappedExpression.java new file mode 100644 index 0000000000..c06bbe1132 --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/spring/expression/wrapper/WrappedExpression.java @@ -0,0 +1,292 @@ +/******************************************************************************* + * (c) Copyright 2020 Micro Focus or one of its affiliates, a Micro Focus company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + ******************************************************************************/ +package com.fortify.cli.common.spring.expression.wrapper; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; + +/** + *

This is a simple wrapper class for a Spring {@link Expression} + * instance. This class is used as a based class for both + * {@link SimpleExpression} and {@link TemplateExpression}.

+ */ +public class WrappedExpression implements Expression { + private final Expression target; + + /** + * Constructor for configuring the expression to be wrapped + * @param target {@link Expression} to be wrapped + */ + public WrappedExpression(Expression target) { + this.target = target; + } + + /** + * @see org.springframework.expression.Expression#getValue() + * @return The evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public Object getValue() throws EvaluationException { + return target.getValue(); + } + + /** + * @see org.springframework.expression.Expression#getValue(Object) + * @param rootObject the root object against which to evaluate the expression + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public Object getValue(Object rootObject) throws EvaluationException { + return target.getValue(rootObject); + } + + /** + * @see org.springframework.expression.Expression#getValue(java.lang.Class) + * @param desiredResultType the class the caller would like the result to be + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public T getValue(Class desiredResultType) throws EvaluationException { + return target.getValue(desiredResultType); + } + + /** + * @see org.springframework.expression.Expression#getValue(Object, java.lang.Class) + * @param rootObject the root object against which to evaluate the expression + * @param desiredResultType the class the caller would like the result to be + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public T getValue(Object rootObject,Class desiredResultType) throws EvaluationException { + return target.getValue(rootObject, desiredResultType); + } + + /** + * @see org.springframework.expression.Expression#getValue(EvaluationContext) + * @param context the context in which to evaluate the expression + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + * + */ + public Object getValue(EvaluationContext context) throws EvaluationException { + return target.getValue(context); + } + + /** + * @see org.springframework.expression.Expression#getValue(EvaluationContext, Object) + * @param context the context in which to evaluate the expression + * @param rootObject the root object against which to evaluate the expression + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + * + */ + public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + return target.getValue(context, rootObject); + } + + /** + * @see org.springframework.expression.Expression#getValue(EvaluationContext, java.lang.Class) + * @param context the context in which to evaluate the expression + * @param desiredResultType the class the caller would like the result to be + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { + return target.getValue(context, desiredResultType); + } + + /** + * @see org.springframework.expression.Expression#getValue(EvaluationContext, Object, java.lang.Class) + * @param context the context in which to evaluate the expression + * @param rootObject the root object against which to evaluate the expression + * @param desiredResultType the class the caller would like the result to be + * @return the evaluation result + * @throws EvaluationException if there is a problem during evaluation + */ + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + return target.getValue(context, rootObject, desiredResultType); + } + + /** + * @see org.springframework.expression.Expression#getValueType() + * @return the most general type of value that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public Class getValueType() throws EvaluationException { + return target.getValueType(); + } + + /** + * @see org.springframework.expression.Expression#getValueType(Object) + * @param rootObject the root object against which to evaluate the expression + * @return the most general type of value that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public Class getValueType(Object rootObject) throws EvaluationException { + return target.getValueType(rootObject); + } + + /** + * @see org.springframework.expression.Expression#getValueType(EvaluationContext) + * @param context the context in which to evaluate the expression + * @return the most general type of value that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public Class getValueType(EvaluationContext context) throws EvaluationException { + return target.getValueType(context); + } + + /** + * @see org.springframework.expression.Expression#getValueType(EvaluationContext, Object) + * @param context the context in which to evaluate the expression + * @param rootObject the root object against which to evaluate the expression + * @return the most general type of value that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + return target.getValueType(context, rootObject); + } + + /** + * @see org.springframework.expression.Expression#getValueTypeDescriptor() + * @return a type descriptor for values that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { + return target.getValueTypeDescriptor(); + } + + /** + * @see org.springframework.expression.Expression#getValueTypeDescriptor(Object) + * @param rootObject the root object against which to evaluate the expression + * @return a type descriptor for values that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + return target.getValueTypeDescriptor(rootObject); + } + + /** + * @see org.springframework.expression.Expression#getValueTypeDescriptor(EvaluationContext) + * @param context the context in which to evaluate the expression + * @return a type descriptor for values that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { + return target.getValueTypeDescriptor(context); + } + + /** + * @see org.springframework.expression.Expression#getValueTypeDescriptor(EvaluationContext, Object) + * @param context the context in which to evaluate the expression + * @param rootObject the root object against which to evaluate the expression + * @return a type descriptor for values that can be set on this context + * @throws EvaluationException if there is a problem determining the type + */ + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + return target.getValueTypeDescriptor(context, rootObject); + } + + /** + * @see org.springframework.expression.Expression#isWritable(EvaluationContext) + * @param context the context in which the expression should be checked + * @return {@code true} if the expression is writable; {@code false} otherwise + * @throws EvaluationException if there is a problem determining if it is writable + */ + public boolean isWritable(EvaluationContext context) throws EvaluationException { + return target.isWritable(context); + } + + /** + * @see org.springframework.expression.Expression#isWritable(EvaluationContext, Object) + * @param context the context in which the expression should be checked + * @param rootObject the root object against which to evaluate the expression + * @return {@code true} if the expression is writable; {@code false} otherwise + * @throws EvaluationException if there is a problem determining if it is writable + */ + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + return target.isWritable(context, rootObject); + } + + /** + * @see org.springframework.expression.Expression#isWritable(Object) + * @param rootObject the root object against which to evaluate the expression + * @return {@code true} if the expression is writable; {@code false} otherwise + * @throws EvaluationException if there is a problem determining if it is writable + */ + public boolean isWritable(Object rootObject) throws EvaluationException { + return target.isWritable(rootObject); + } + + /** + * @see org.springframework.expression.Expression#setValue(EvaluationContext, Object) + * @param context the context in which to set the value of the expression + * @param value the new value + * @throws EvaluationException if there is a problem during evaluation + */ + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + target.setValue(context, value); + } + + /** + * @see org.springframework.expression.Expression#setValue(Object, Object) + * @param rootObject the root object against which to evaluate the expression + * @param value the new value + * @throws EvaluationException if there is a problem during evaluation + */ + public void setValue(Object rootObject, Object value) throws EvaluationException { + target.setValue(rootObject, value); + } + + /** + * @see org.springframework.expression.Expression#setValue(EvaluationContext, Object, Object) + * @param context the context in which to set the value of the expression + * @param rootObject the root object against which to evaluate the expression + * @param value the new value + * @throws EvaluationException if there is a problem during evaluation + */ + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + target.setValue(context, rootObject, value); + } + + /** + * @see org.springframework.expression.Expression#getExpressionString() + * @return the original expression string + */ + public String getExpressionString() { + return target.getExpressionString(); + } + + /** + * @return String representation for this {@link WrappedExpression} + */ + @Override + public String toString() { + return this.getClass().getSimpleName()+"("+getExpressionString()+")"; + } + +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/_main/cli/cmd/SCSastCommands.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/_main/cli/cmd/SCSastCommands.java index 299f8bf401..52b45d2985 100644 --- a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/_main/cli/cmd/SCSastCommands.java +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/_main/cli/cmd/SCSastCommands.java @@ -14,6 +14,7 @@ import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; import com.fortify.cli.sc_sast._common.session.cli.cmd.SCSastSessionCommands; +import com.fortify.cli.sc_sast.data_extract.cli.cmd.SCSastDataExtractCommands; import com.fortify.cli.sc_sast.rest.cli.cmd.SCSastControllerRestCommands; import com.fortify.cli.sc_sast.scan.cli.cmd.SCSastScanCommands; import com.fortify.cli.sc_sast.sensor.cli.cmd.SCSastSensorCommands; @@ -33,6 +34,7 @@ // 'rest' has a different header ('Interact with' compared to most // other commands ('Manage'). SCSastSessionCommands.class, + SCSastDataExtractCommands.class, SCSastScanCommands.class, SCSastSensorCommands.class, SCSastControllerRestCommands.class, diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractCreateCommand.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractCreateCommand.java new file mode 100644 index 0000000000..560a4eab9f --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractCreateCommand.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * 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.sc_sast.data_extract._common.cli.cmd; + +import java.util.HashMap; +import java.util.Map; + +import com.fortify.cli.common.cli.cmd.AbstractRunnableCommand; +import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateExecutor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateHelper; + +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +public abstract class AbstractDataExtractCreateCommand extends AbstractRunnableCommand implements Runnable { + @Option(names={"-t", "--template"}, required=true) + private String template; + @Option(names={"-p", "--parameters"}, required=false, split=",") + private Map parameters = new HashMap<>(); + @Mixin private ProgressWriterFactoryMixin progressWriterFactory; + + @Override + public final void run() { + initMixins(); + try ( var progressWriter = progressWriterFactory.create() ) { + progressWriter.writeProgress("Loading template %s", template); + var templateDescriptor = DataExtractTemplateHelper.load(getType(), template); + progressWriter.writeProgress("Executing template %s", template); + var templateExecutor = DataExtractTemplateExecutor.builder() + .template(templateDescriptor) + .inputParameters(parameters) + .progressWriter(progressWriter) + .build(); + configure(templateExecutor); + templateExecutor.execute(); + /* + progressWriter.writeProgress("Evaluating parameters"); + var parameterValues = getParameterHelper().buildObjectNode(templateDescriptor, parameters); + DataExtractRunner.builder() + .progressWriter(progressWriter) + .data(parameterValues) + .restHelpers(toMap(getRestHelpers(), DataExtractRestHelper::getName)) + .template(templateDescriptor) + .build().run(); + */ + } + } + + protected abstract String getType(); + protected abstract void configure(DataExtractTemplateExecutor templateExecutor); + + /* + @Builder + private static final class DataExtractRunner implements Runnable { + private final IProgressWriterI18n progressWriter; + private final DataExtractTemplateDescriptor template; + private final ObjectNode data; + private final Map restHelpers; + + @Override + public void run() { + template.getRequests().forEach(this::executeRequest); + } + + private final void executeRequest(DataExtractTemplateRequestDescriptor requestDescriptor) { + var uri = JsonHelper.evaluateSpelExpression(data, requestDescriptor.getUri(), String.class); + var query = evaluateQuery(requestDescriptor.getQuery()); + var restHelper = getRestHelper(requestDescriptor); + var unirest = restHelper.getUnirestInstance(); + var nextPageUrlProducer = restHelper.getNextPageUrlProducer(); + var request = unirest.get(uri).queryString(query); + if ( nextPageUrlProducer!=null ) { + PagingHelper.processPages(unirest, request, nextPageUrlProducer, r->processResponse(requestDescriptor, r)); + } else { + processResponse(requestDescriptor, request.asObject(JsonNode.class)); + } + } + + private final void processResponse(DataExtractTemplateRequestDescriptor requestDescriptor, HttpResponse response) { + var name = requestDescriptor.getName(); + var rawBody = response.getBody(); + var body = getRestHelper(requestDescriptor).getInputTransformer().transformInput(rawBody); + data.set(name+"_raw", rawBody); + data.set(name, body); + + // TODO Process forEach + System.out.println(body.toPrettyString()); + } + + // TODO Instead of Map, create separate DataExtractRestHelpers class, + // with add() methods for adding helpers, and this get-method for retrieval. + private DataExtractRestHelper getRestHelper(DataExtractTemplateRequestDescriptor requestDescriptor) { + var name = requestDescriptor.getName(); + var from = requestDescriptor.getFrom(); + if ( StringUtils.isBlank(from) ) { + if ( restHelpers.size()==1 ) { + return restHelpers.values().iterator().next(); + } else { + throw new IllegalStateException(String.format("Required 'from:' property (allowed values: %s) missing for request %s", from, restHelpers.keySet(), name)); + } + } + var result = restHelpers.get(requestDescriptor.getFrom()); + if ( result==null ) { + throw new IllegalStateException(String.format("Invalid 'from: %s' for request %s, allowed values: %s", from, name, restHelpers.keySet())); + } + return result; + } + + private Map evaluateQuery(Map queryExpressions) { + Map result = new LinkedHashMap<>(); + if ( queryExpressions!=null ) { + queryExpressions.entrySet().forEach(e->result.put(e.getKey(), JsonHelper.evaluateSpelExpression(data, e.getValue(), String.class))); + } + return result; + } + } + */ +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractListTemplatesCommand.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractListTemplatesCommand.java new file mode 100644 index 0000000000..1f43f133b1 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/cli/cmd/AbstractDataExtractListTemplatesCommand.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.sc_sast.data_extract._common.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; +import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; + +public abstract class AbstractDataExtractListTemplatesCommand extends AbstractOutputCommand implements IJsonNodeSupplier { + @Override + public final JsonNode getJsonNode() { + // TODO Auto-generated method stub + return null; + } + @Override + public final boolean isSingular() { + return false; + } + protected abstract String getType(); + + +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractRestHelper.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractRestHelper.java new file mode 100644 index 0000000000..7d208cbeec --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractRestHelper.java @@ -0,0 +1,55 @@ +/** + * Copyright 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.sc_sast.data_extract._common.helper; + +import com.fortify.cli.common.output.product.IProductHelper; +import com.fortify.cli.common.output.transform.IInputTransformer; +import com.fortify.cli.common.output.transform.IRecordTransformer; +import com.fortify.cli.common.rest.paging.INextPageUrlProducer; +import com.fortify.cli.common.rest.paging.INextPageUrlProducerSupplier; +import com.fortify.cli.common.rest.unirest.IUnirestInstanceSupplier; +import com.fortify.cli.common.util.JavaHelper; + +import kong.unirest.UnirestInstance; +import lombok.Data; + +@Data +public final class DataExtractRestHelper implements AutoCloseable { + private final String name; + private final IUnirestInstanceSupplier unirestInstanceSupplier; + private final IProductHelper productHelper; + private UnirestInstance unirestInstance; + public final UnirestInstance getUnirestInstance() { + if ( unirestInstance==null ) { + unirestInstance = unirestInstanceSupplier.getUnirestInstance(); + } + return unirestInstance; + } + public final IInputTransformer getInputTransformer() { + return JavaHelper.as(productHelper, IInputTransformer.class).orElse(i->i); + } + public final IRecordTransformer getRecordTransformer() { + return JavaHelper.as(productHelper, IRecordTransformer.class).orElse(r->r); + } + public final INextPageUrlProducer getNextPageUrlProducer() { + return JavaHelper.as(productHelper, INextPageUrlProducerSupplier.class) + .map(s->s.getNextPageUrlProducer()) + .orElse(null); + } + @Override + public void close() throws Exception { + if ( unirestInstance!=null ) { + unirestInstance.close(); + } + } +} \ No newline at end of file diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateDescriptor.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateDescriptor.java new file mode 100644 index 0000000000..708190b4b8 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateDescriptor.java @@ -0,0 +1,266 @@ +/** + * Copyright 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.sc_sast.data_extract._common.helper; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.spring.expression.wrapper.SimpleExpression; +import com.fortify.cli.common.spring.expression.wrapper.TemplateExpression; +import com.fortify.cli.common.util.StringUtils; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * This class describes a data extraction template deserialized from a + * data extraction template YAML file, containing elements describing: + *
    + *
  • Template metadata like name and description
  • + *
  • Data to be processed like template parameters and + * responses from REST calls
  • + *
  • Template output
  • + *
+ * + * @author Ruud Senden + */ +@Reflectable @NoArgsConstructor +@Data +public class DataExtractTemplateDescriptor { + /** Template name, set in {@link #postLoad(String)} method */ + private String name; + /** Template description */ + private String description; + /** Template data sources, evaluated in the order as defined in the YAML file */ + private List sources; + + /** + * This method is invoked by DataExtractTemplateHelper after deserializing + * an instance of this class from a YAML file. It performs some additional + * initialization and validation. + */ + final void postLoad(String name) { + this.name = name; + checkNotNull("template sources", sources, this); + sources.forEach(d->d.postLoad(null)); + } + + /** + * Get the list of all (top-level) {@link DataExtractTemplateSourceParameterDescriptor} + * instances, describing all template parameters. + */ + public final List getParameterDescriptors() { + return sources.stream() + .map(DataExtractTemplateSourceDescriptor::getParameters) + .filter(Objects::nonNull) + .flatMap(l->l.stream()) + .collect(Collectors.toList()); + } + + /** + * Utility method for checking whether the given value is not blank, throwing an + * exception otherwise. + * @param property Descriptive name of the YAML property being checked + * @param value Value to be checked for not being blank + * @param entity The object containing the property to be checked + */ + private static final void checkNotBlank(String property, String value, Object entity) { + if ( StringUtils.isBlank(value) ) { + throw new TemplateValidationException(String.format("Template %s property must be specified (entity: %s)", property, entity.toString())); + } + } + + /** + * Utility method for checking whether the given value is not null, throwing an + * exception otherwise. + * @param property Descriptive name of the YAML property being checked + * @param value Value to be checked for not being null + * @param entity The object containing the property to be checked + */ + private static final void checkNotNull(String property, Object value, Object entity) { + if ( value==null ) { + throw new TemplateValidationException(String.format("Template %s property must be specified (entity: %s)", property, entity.toString())); + } + } + + /** + * Interface to be implemented by all template data elements. + */ + public static interface IDataExtractTemplateSource { + public IDataExtractTemplateSource getParent(); + } + + /** + * This class describes a single source element, which may contain zero or more + * parameters or requests. This class is used for both top-level source elements, + * and source elements in forEach elements. Parameters may only be contained in + * top-level source elements, not within forEach elements. Potentially, later + * versions may add support for other sources, like static values or arrays, + * allowing for example iteration over a static array. + * + * @author Ruud Senden + */ + @Reflectable @NoArgsConstructor + @Data + public static final class DataExtractTemplateSourceDescriptor implements IDataExtractTemplateSource { + /** The parent for this source element, or null for top-level element */ + @ToString.Exclude @EqualsAndHashCode.Exclude private IDataExtractTemplateSource parent; + /** Optional parameters for this source element */ + private List parameters; + /** Optional requests for this source element */ + private List requests; + + /** + * This method is invoked by the parent element (which may either be another + * source element, or the top-level {@link DataExtractTemplateDescriptor} instance). + * It stores the given parent, then invokes the postLoad() method on each + * parameter and request. + * @param parent of this element, or null if the parent is the top-level + * {@link DataExtractTemplateDescriptor} + */ + public final void postLoad(IDataExtractTemplateSource parent) { + this.parent = parent; + if ( parameters!=null ) { parameters.forEach(d->d.postLoad(this)); } + if ( requests!=null ) { requests.forEach(d->d.postLoad(this)); } + } + } + + /** + * Abstract base class for all source elements like individual parameter or request descriptors. + * This includes the parent source element (set through {@link #postLoad(IDataExtractTemplateSource)}), + * a required source name, an optional description, and optional forEach block. + */ + @Reflectable @NoArgsConstructor + @Data + public static abstract class AbstractDataExtractTemplateSourceDescriptor implements IDataExtractTemplateSource { + /** The parent for this source element, never null as source elements are always contained in a {@link DataExtractTemplateSourceDescriptor} instance */ + @ToString.Exclude @EqualsAndHashCode.Exclude private IDataExtractTemplateSource parent; + /** Required name for this source element */ + private String name; + /** Optional description for this source element */ + private String description; + /** Optional forEach block, listing sources to be repeated for each entry produced by this data element */ + private DataExtractTemplateSourceForEachDescriptor forEach; + + /** + * This method is invoked by the parent {@link DataExtractTemplateSourceDescriptor} + * instance. It stores the given parent, then invokes the postLoad() method on each + * forEach element, and finally calls the _postLoad() method provided by subclasses. + * @param parent {@link DataExtractTemplateSourceDescriptor} instance + */ + public final void postLoad(IDataExtractTemplateSource parent) { + this.parent = parent; + if ( forEach!=null ) { forEach.postLoad(this); } + _postLoad(); + } + + /** + * Abstract method that allows subclasses to perform post-load processing + */ + protected abstract void _postLoad(); + } + + /** + * 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 + * template parameter. + */ + @Reflectable @NoArgsConstructor + @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) + public static final class DataExtractTemplateSourceForEachDescriptor extends AbstractDataExtractTemplateSourceDescriptor { + /** Optional (sub-)property/expression from which to retrieve the values to be iterated through */ + private SimpleExpression property; + /** Sources to be repeated for each value */ + private List sources; + + /** + * This method is invoked by {@link AbstractDataExtractTemplateSourceDescriptor#postLoad(IDataExtractTemplateSource)} + * method. It checks that required properties are set, then calls the postLoad() method for + * each sub-source. + */ + protected final void _postLoad() { + checkNotBlank("forEach name", getName(), this); + checkNotNull("forEach sources", sources, this); + sources.forEach(d->d.postLoad(this)); + } + } + + /** + * This class describes a template parameter. + */ + @Reflectable @NoArgsConstructor + @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) + public static final class DataExtractTemplateSourceParameterDescriptor extends AbstractDataExtractTemplateSourceDescriptor { + /** Optional parameter type */ + private String type; + /** Optional type parameters*/ + private Map typeParameters; + /** Optional template expression defining the default parameter value if not provided by user */ + private TemplateExpression defaultValue; + /** Boolean indicating whether this parameter is required */ + private boolean required = true; + + /** + * This method is invoked by {@link AbstractDataExtractTemplateSourceDescriptor#postLoad(IDataExtractTemplateSource)} + * method. It checks that required properties are set, and that this parameter is defined as + * a top-level source. + */ + protected final void _postLoad() { + checkNotBlank("parameter name", getName(), this); + var grandParent = getParent().getParent(); + if ( grandParent!=null ) { + throw new TemplateValidationException("Template parameters must be defined in a top-level data element"); + } + } + } + + /** + * This class describes a REST request. + */ + @Reflectable @NoArgsConstructor + @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) + public static final class DataExtractTemplateSourceRequestDescriptor extends AbstractDataExtractTemplateSourceDescriptor { + /** Required template expression defining the request URI from which to get the data */ + private TemplateExpression uri; + /** Define from which remote system the data should be retrieved. Required if multiple + * remote systems are available, optional if there's only a single remote system. */ + private String from; + /** Map defining (non-encoded) request query parameters; parameter values are defined as template expressions */ + private Map query; + + /** + * This method is invoked by {@link AbstractDataExtractTemplateSourceDescriptor#postLoad(IDataExtractTemplateSource)} + * method. It checks that required properties are set. + */ + protected final void _postLoad() { + checkNotBlank("request name", getName(), this); + checkNotNull("request uri", uri, this); + } + } + + /** + * Exception class used for template validation errors. + */ + public static final class TemplateValidationException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + public TemplateValidationException(String msg) { + super(msg); + } + } +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateExecutor.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateExecutor.java new file mode 100644 index 0000000000..d6481e9059 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateExecutor.java @@ -0,0 +1,230 @@ +/** + * Copyright 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.sc_sast.data_extract._common.helper; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.FloatNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.product.IProductHelper; +import com.fortify.cli.common.progress.helper.IProgressWriterI18n; +import com.fortify.cli.common.rest.paging.PagingHelper; +import com.fortify.cli.common.rest.unirest.IUnirestInstanceSupplier; +import com.fortify.cli.common.spring.expression.SpelHelper; +import com.fortify.cli.common.spring.expression.wrapper.TemplateExpression; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.DataExtractTemplateSourceDescriptor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.DataExtractTemplateSourceForEachDescriptor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.DataExtractTemplateSourceParameterDescriptor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.DataExtractTemplateSourceRequestDescriptor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.TemplateValidationException; + +import kong.unirest.HttpResponse; +import lombok.Builder; +import lombok.Data; + +@Builder +public class DataExtractTemplateExecutor { + private static final ObjectMapper objectMapper = JsonHelper.getObjectMapper(); + private final IProgressWriterI18n progressWriter; + private final DataExtractTemplateDescriptor template; + private final Map inputParameters; + private final ObjectNode data = objectMapper.createObjectNode(); + private final Map> parameterConverters = createDefaultParameterConverters(); + private final Map restHelpers = new HashMap<>(); + + public final void execute() { + processParameters(); + processSources(); + } + + public final DataExtractTemplateExecutor addParameterConverter(String type, BiFunction converter) { + parameterConverters.put(type, converter); + return this; + } + public final DataExtractTemplateExecutor addParameterConverter(String type, Function converter) { + parameterConverters.put(type, (v,a)->converter.apply(v)); + return this; + } + public final DataExtractTemplateExecutor addRestHelper(String name, IUnirestInstanceSupplier unirestInstanceSupplier, IProductHelper productHelper) { + restHelpers.put(name, new DataExtractRestHelper(name, unirestInstanceSupplier, productHelper)); + return this; + } + + private final void processParameters() { + template.getParameterDescriptors().forEach(this::processParameter); + } + + private final void processParameter(DataExtractTemplateSourceParameterDescriptor parameter) { + var name = parameter.getName(); + var value = inputParameters.get(name); + if ( value==null ) { + var defaultValueExpression = parameter.getDefaultValue(); + value = defaultValueExpression==null + ? null + : JsonHelper.evaluateSpelExpression(data, defaultValueExpression, String.class); + } + data.set(name, convertParameterValue(value, parameter)); + } + + private JsonNode convertParameterValue(String value, DataExtractTemplateSourceParameterDescriptor parameter) { + var name = parameter.getName(); + var type = StringUtils.isBlank(parameter.getType()) ? "string" : parameter.getType(); + var required = parameter.isRequired(); + if ( StringUtils.isBlank(value) && required ) { + throw new IllegalArgumentException(String.format("Required parameter %s not specified", name)); + } + if ( value==null ) { return NullNode.instance; } + var paramConverter = parameterConverters.get(type); + if ( paramConverter==null ) { + throw new TemplateValidationException(String.format("Unknown parameter type %s for parameter %s", type, name)); + } else { + var args = ParameterTypeConverterArgs.builder() + .progressWriter(progressWriter) + .template(template) + .parameter(parameter) + .data(data) + .build(); + return paramConverter.apply(value, args); + } + } + + private final void processSources() { + template.getSources().forEach(s->processSource(s, data)); + } + + private final void processSource(DataExtractTemplateSourceDescriptor source, ObjectNode data) { + if ( source.getParameters()!=null ) { source.getParameters().forEach(p->processSource(p, data)); } + if ( source.getRequests()!=null ) { source.getRequests().forEach(p->processSource(p, data)); } + } + + private final void processSource(DataExtractTemplateSourceParameterDescriptor source, ObjectNode data) { + // Parameter values have already been inserted into the data object, so just need to + // handle forEach + processSourceForEach(source.getName(), source.getForEach(), data); + } + + private final void processSource(DataExtractTemplateSourceRequestDescriptor source, ObjectNode data) { + executeRequest(source, data); + } + + private final void processSourceForEach(String parentName, DataExtractTemplateSourceForEachDescriptor forEach, ObjectNode data) { + if ( forEach!=null ) { + var property = forEach.getProperty()==null ? SpelHelper.parseSimpleExpression(parentName) : forEach.getProperty(); + var input = JsonHelper.evaluateSpelExpression(data, property, JsonNode.class); + if ( input!=null ) { + var childName = forEach.getName(); + if ( input instanceof ArrayNode ) { + processSourceForEach(childName, (ArrayNode)input, data, forEach.getSources()); + } else { + throw new TemplateValidationException("forEach not supported on node type "+input.getNodeType()); + } + } + } + } + + private final void processSourceForEach(String name, ArrayNode source, ObjectNode data, List sources) { + for ( int i = 0 ; i < source.size(); i++ ) { + var currentObject = source.get(i); + var newData = data.deepCopy(); + newData.set(name, currentObject); + sources.forEach(s->processSource(s, newData)); + } + } + + private final void executeRequest(DataExtractTemplateSourceRequestDescriptor requestDescriptor, ObjectNode data) { + var uri = JsonHelper.evaluateSpelExpression(data, requestDescriptor.getUri(), String.class); + var query = evaluateQuery(requestDescriptor.getQuery()); + var restHelper = getRestHelper(requestDescriptor); + var unirest = restHelper.getUnirestInstance(); + var nextPageUrlProducer = restHelper.getNextPageUrlProducer(); + var request = unirest.get(uri).queryString(query); + if ( nextPageUrlProducer!=null ) { + PagingHelper.processPages(unirest, request, nextPageUrlProducer, r->processResponse(requestDescriptor, r)); + } else { + processResponse(requestDescriptor, request.asObject(JsonNode.class)); + } + } + + private final void processResponse(DataExtractTemplateSourceRequestDescriptor requestDescriptor, HttpResponse response) { + var name = requestDescriptor.getName(); + var rawBody = response.getBody(); + var body = getRestHelper(requestDescriptor).getInputTransformer().transformInput(rawBody); + data.set(name+"_raw", rawBody); + data.set(name, body); + processSourceForEach(requestDescriptor.getName(), requestDescriptor.getForEach(), data); + System.out.println(body.toPrettyString()); + } + + // TODO Instead of Map, create separate DataExtractRestHelpers class, + // with add() methods for adding helpers, and this get-method for retrieval. + private DataExtractRestHelper getRestHelper(DataExtractTemplateSourceRequestDescriptor requestDescriptor) { + var name = requestDescriptor.getName(); + var from = requestDescriptor.getFrom(); + if ( StringUtils.isBlank(from) ) { + if ( restHelpers.size()==1 ) { + return restHelpers.values().iterator().next(); + } else { + throw new IllegalStateException(String.format("Required 'from:' property (allowed values: %s) missing for request %s", from, restHelpers.keySet(), name)); + } + } + var result = restHelpers.get(requestDescriptor.getFrom()); + if ( result==null ) { + throw new IllegalStateException(String.format("Invalid 'from: %s' for request %s, allowed values: %s", from, name, restHelpers.keySet())); + } + return result; + } + + private Map evaluateQuery(Map queryExpressions) { + Map result = new LinkedHashMap<>(); + if ( queryExpressions!=null ) { + queryExpressions.entrySet().forEach(e->result.put(e.getKey(), JsonHelper.evaluateSpelExpression(data, e.getValue(), String.class))); + } + return result; + } + + @Builder @Data + public static final class ParameterTypeConverterArgs { + private final IProgressWriterI18n progressWriter; + private final DataExtractTemplateDescriptor template; + private final DataExtractTemplateSourceParameterDescriptor parameter; + private final ObjectNode data; + } + + private static final Map> createDefaultParameterConverters() { + Map> result = new HashMap<>(); + result.put("string", (v,a)->new TextNode(v)); + result.put("boolean", (v,a)->BooleanNode.valueOf(Boolean.parseBoolean(v))); + result.put("int", (v,a)->IntNode.valueOf(Integer.parseInt(v))); + result.put("long", (v,a)->LongNode.valueOf(Long.parseLong(v))); + result.put("double", (v,a)->DoubleNode.valueOf(Double.parseDouble(v))); + result.put("float", (v,a)->FloatNode.valueOf(Float.parseFloat(v))); + // TODO Add BigIntegerNode/DecimalNode/ShortNode support? + return result; + } +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateHelper.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateHelper.java new file mode 100644 index 0000000000..b4ae591fc4 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/_common/helper/DataExtractTemplateHelper.java @@ -0,0 +1,86 @@ +/** + * Copyright 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.sc_sast.data_extract._common.helper; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fortify.cli.common.util.FileUtils; + +import lombok.SneakyThrows; + +public class DataExtractTemplateHelper { + private static final ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); + private DataExtractTemplateHelper() {} + + public static final DataExtractTemplateDescriptor load(String type, String name) { + var resourceFile = getTemplateResourceFile(type, name); + try ( var is = FileUtils.getResourceInputStream(resourceFile) ) { + return load(resourceFile, is); + } catch ( IOException e ) { + throw new RuntimeException("Error loading template "+name, e); + } + } + + public static final Stream list(String type) { + return Stream.of(getTemplateResources(type)) + .map(DataExtractTemplateHelper::load); + } + + private static final String getTemplatesResourceDir(String type) { + return String.format("com/fortify/cli/%s/data_extract/templates", type.toLowerCase().replace('-', '_')); + } + + private static final String getTemplateResourceFile(String type, String name) { + return String.format("%s/%s.yaml", getTemplatesResourceDir(type), name); + } + + @SneakyThrows + private static final Resource[] getTemplateResources(String type) { + var matchPattern = String.format("%s/*.yaml", getTemplatesResourceDir(type)); + return new PathMatchingResourcePatternResolver().getResources(matchPattern); + } + + private static final DataExtractTemplateDescriptor load(Resource resource) { + try ( var is = resource.getInputStream() ) { + return load(resource.getFilename(), is); + } catch (IOException e) { + throw new RuntimeException("Error loading template "+resource.getFilename(), e); + } + } + + private static final DataExtractTemplateDescriptor load(String fileName, InputStream is) { + if ( is==null ) { + // TODO Use more descriptive exception message + throw new IllegalStateException("Can't read "+fileName); + } + try { + var result = yamlObjectMapper.readValue(is, DataExtractTemplateDescriptor.class); + result.postLoad(getTemplateName(fileName)); + return result; + } catch (IOException e) { + throw new RuntimeException("Error loading template "+fileName, e); + } + } + + private static final String getTemplateName(String fileName) { + return Path.of(fileName).getFileName().toString().replace(".yaml", ""); + } +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCommands.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCommands.java new file mode 100644 index 0000000000..2f0eb66b36 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCommands.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.sc_sast.data_extract.cli.cmd; + +import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; + +import picocli.CommandLine.Command; + +@Command( + name = "data-extract", + subcommands = { + SCSastDataExtractCreateCommand.class, + } +) +public class SCSastDataExtractCommands extends AbstractContainerCommand { +} diff --git a/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCreateCommand.java b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCreateCommand.java new file mode 100644 index 0000000000..3ae7e8816a --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/java/com/fortify/cli/sc_sast/data_extract/cli/cmd/SCSastDataExtractCreateCommand.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * 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.sc_sast.data_extract.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.spring.expression.SpelHelper; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.sc_sast._common.rest.helper.SCSastControllerProductHelper; +import com.fortify.cli.sc_sast._common.rest.helper.SCSastSSCProductHelper; +import com.fortify.cli.sc_sast._common.session.cli.mixin.SCSastUnirestInstanceSupplierMixin; +import com.fortify.cli.sc_sast.data_extract._common.cli.cmd.AbstractDataExtractCreateCommand; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateDescriptor.TemplateValidationException; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateExecutor; +import com.fortify.cli.sc_sast.data_extract._common.helper.DataExtractTemplateExecutor.ParameterTypeConverterArgs; +import com.fortify.cli.ssc.appversion.helper.SSCAppVersionHelper; +import com.fortify.cli.ssc.issue.helper.SSCIssueFilterSetHelper; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.Create.CMD_NAME) +public class SCSastDataExtractCreateCommand extends AbstractDataExtractCreateCommand { + @Getter @Mixin private SCSastUnirestInstanceSupplierMixin unirestInstanceSupplier; + + @Override + protected final String getType() { + return "SC-SAST"; + } + + @Override + protected void configure(DataExtractTemplateExecutor templateExecutor) { + templateExecutor + .addParameterConverter("appversion_single", this::loadAppVersion) + .addParameterConverter("filterset", this::loadFilterSet) + .addRestHelper("ssc", unirestInstanceSupplier::getSscUnirestInstance, SCSastSSCProductHelper.INSTANCE) + .addRestHelper("sc-sast", unirestInstanceSupplier::getControllerUnirestInstance, SCSastControllerProductHelper.INSTANCE); + } + + private final JsonNode loadAppVersion(String nameOrId, ParameterTypeConverterArgs args) { + args.getProgressWriter().writeProgress("Loading application version %s", nameOrId); + return SSCAppVersionHelper.getRequiredAppVersion(unirestInstanceSupplier.getSscUnirestInstance(), nameOrId, ":").asJsonNode(); + } + + private final JsonNode loadFilterSet(String titleOrId, ParameterTypeConverterArgs args) { + args.getProgressWriter().writeProgress("Loading filter set %s", titleOrId); + var parameter = args.getParameter(); + var typeParameters = parameter.getTypeParameters(); + var appVersionIdExpression = typeParameters==null ? null : typeParameters.get("appversion.id"); + if ( appVersionIdExpression==null ) { + appVersionIdExpression = SpelHelper.parseTemplateExpression("${appversion?.id}"); + } + var appVersionId = JsonHelper.evaluateSpelExpression(args.getData(), appVersionIdExpression, String.class); + if ( StringUtils.isBlank(appVersionId) ) { + throw new TemplateValidationException(String.format("Template parameter %s requires ${%s} to be available", parameter.getName(), appVersionIdExpression.getExpressionString())); + } + return new SSCIssueFilterSetHelper(unirestInstanceSupplier.getSscUnirestInstance(), appVersionId).getDescriptorByTitleOrId(titleOrId, false).asJsonNode(); + } + + /* + @Override + protected final List getRestHelpers() { + return Arrays.asList( + new DataExtractRestHelper("ssc", unirestInstanceSupplier::getSscUnirestInstance, SCSastSSCProductHelper.INSTANCE), + new DataExtractRestHelper("sc-sast", unirestInstanceSupplier::getControllerUnirestInstance, SCSastControllerProductHelper.INSTANCE) + ); + } + + @Override + protected final List getParamHelpers() { + return Arrays.asList( + new DataExtractParameterHelper("appversion_single", v-> + SSCAppVersionHelper.getRequiredAppVersion(unirestInstanceSupplier.getSscUnirestInstance(), v, ":").asJsonNode()) + ); + } + */ +} diff --git a/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/data_extract/templates/SC-SAST.zip b/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/data_extract/templates/SC-SAST.zip deleted file mode 100644 index 1b82ba32a22f924c0992f82213ceb0041b8bc8f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200 zcmWIWW@h1H00Dj%j|eaWN=P!uFr;UeWRxc9Cg-Q5>J}#_=H+GPrR!BD=H`Tka56CW zHXlg^;nE6j21b?_%nS@*BEXxGNsbwpp%QRAmNbHxc&uQBSb0l6t F0RY0&CsY6c diff --git a/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/data_extract/templates/github-code-scanning.yaml b/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/data_extract/templates/github-code-scanning.yaml new file mode 100644 index 0000000000..fb6744ea71 --- /dev/null +++ b/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/data_extract/templates/github-code-scanning.yaml @@ -0,0 +1,24 @@ +description: | + bla + +sources: + - parameters: + - name: appversion + type: appversion_single + - name: filterset + type: filterset + + - requests: + - name: issues + uri: /api/v1/projectVersions/${appversion.id}/issues?limit=200 + query: + filter: ISSUE[11111111-1111-1111-1111-111111111151]:SCA + filterset: ${filterset.guid} + from: ssc + forEach: + name: issue + sources: + - requests: + - name: issue-details + uri: /api/v1/issueDetails/${issue.id} + from: ssc \ No newline at end of file diff --git a/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/i18n/SCSastMessages.properties b/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/i18n/SCSastMessages.properties index 9c79d702ee..c2b599dc69 100644 --- a/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/i18n/SCSastMessages.properties +++ b/fcli-core/fcli-sc-sast/src/main/resources/com/fortify/cli/sc_sast/i18n/SCSastMessages.properties @@ -101,6 +101,12 @@ fcli.sc-sast.session.list.usage.description.1 = For sessions created using user fcli.sc-sast.session.list.usage.description.2 = For sessions created using a pre-generated token, fcli cannot \ display session expiration date or status, as SSC doesn't allow for obtaining this information. +# fcli sc-sast data-extract +fcli.sc-sast.data-extract.usage.header = TODO +fcli.sc-sast.data-extract.create.usage.header = TODO +fcli.sc-sast.data-extract.create.template = TODO +fcli.sc-sast.data-extract.create.parameters = TODO + # fcli sc-sast scan fcli.sc-sast.scan.usage.header = Manage ScanCentral SAST scans. fcli.sc-sast.scan.cancel.usage.header = Cancel a previously submitted scan request.