From e2a6f1e552657cdb485f2bd998233d0641212210 Mon Sep 17 00:00:00 2001 From: Ruud Senden <8635138+rsenden@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:46:20 +0100 Subject: [PATCH] feat: SSC: Add support for importing Debricked results --- ...actSSCAppVersionArtifactUploadCommand.java | 77 ++++++ .../cmd/SSCAppVersionArtifactCommands.java | 2 + .../SSCAppVersionArtifactDownloadCommand.java | 1 - .../SSCAppVersionArtifactUploadCommand.java | 32 +-- ...CAppVersionArtifactImportFromCommands.java | 15 ++ .../debricked/DebrickedLoginOptions.java | 40 ++++ .../debricked/DebrickedUrlConfigOptions.java | 45 ++++ ...ionArtifactImportFromDebrickedCommand.java | 222 ++++++++++++++++++ .../cli/mixin/SSCOutputHelperMixins.java | 6 + .../cli/ssc/i18n/SSCMessages.properties | 10 + 10 files changed, 420 insertions(+), 30 deletions(-) create mode 100644 fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/AbstractSSCAppVersionArtifactUploadCommand.java create mode 100644 fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/SSCAppVersionArtifactImportFromCommands.java create mode 100644 fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedLoginOptions.java create mode 100644 fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedUrlConfigOptions.java create mode 100644 fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/SSCAppVersionArtifactImportFromDebrickedCommand.java diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/AbstractSSCAppVersionArtifactUploadCommand.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/AbstractSSCAppVersionArtifactUploadCommand.java new file mode 100644 index 0000000000..4f86e17eac --- /dev/null +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/AbstractSSCAppVersionArtifactUploadCommand.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * (c) Copyright 2021 Micro Focus or one of its affiliates + * + * 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.ssc.appversion_artifact.cli.cmd; + +import java.io.File; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.cmd.unirest.IUnirestBaseRequestSupplier; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.ssc.appversion.cli.mixin.SSCAppVersionResolverMixin; +import com.fortify.cli.ssc.appversion.helper.SSCAppVersionDescriptor; +import com.fortify.cli.ssc.rest.SSCUrls; + +import io.micronaut.core.annotation.ReflectiveAccess; +import kong.unirest.HttpRequest; +import kong.unirest.HttpRequestWithBody; +import kong.unirest.UnirestInstance; +import picocli.CommandLine.Mixin; + +@ReflectiveAccess +public abstract class AbstractSSCAppVersionArtifactUploadCommand extends AbstractSSCAppVersionArtifactOutputCommand implements IUnirestBaseRequestSupplier { + @Mixin private SSCAppVersionResolverMixin.RequiredOption parentResolver; + + @Override + public final HttpRequest getBaseRequest(UnirestInstance unirest) { + String engineType = getEngineType(); + SSCAppVersionDescriptor av = parentResolver.getAppVersionDescriptor(unirest); + HttpRequestWithBody request = unirest.post(SSCUrls.PROJECT_VERSION_ARTIFACTS(av.getVersionId())); + if ( StringUtils.isNotBlank(engineType) ) { + // TODO Check parser plugin is enabled in SSC + request = request.queryString("engineType", engineType); + } + File file = getFile(); + preUpload(unirest, file); + JsonNode uploadResponse = request.multiPartContent() + .field("file", file) + .asObject(JsonNode.class).getBody(); + postUpload(unirest, file); + String artifactId = JsonHelper.evaluateJsonPath(uploadResponse, "$.data.id", String.class); + // TODO Do we actually show any scan data from the embedded scans? + return unirest.get(SSCUrls.ARTIFACT(artifactId)).queryString("embed","scans"); + } + + @Override + public boolean isSingular() { + return true; + } + + protected abstract String getEngineType(); + protected abstract File getFile(); + + protected void preUpload(UnirestInstance unirest, File file) {} + protected void postUpload(UnirestInstance unirest, File file) {} +} diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactCommands.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactCommands.java index a28218f723..6372695a22 100644 --- a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactCommands.java +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactCommands.java @@ -2,6 +2,7 @@ import com.fortify.cli.common.cli.cmd.AbstractFortifyCLICommand; import com.fortify.cli.common.variable.PredefinedVariable; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.SSCAppVersionArtifactImportFromCommands; import com.fortify.cli.ssc.appversion_artifact.cli.cmd.purge.SSCAppVersionArtifactPurgeCommands; import picocli.CommandLine.Command; @@ -13,6 +14,7 @@ SSCAppVersionArtifactDeleteCommand.class, SSCAppVersionArtifactDownloadCommand.class, SSCAppVersionArtifactGetCommand.class, + SSCAppVersionArtifactImportFromCommands.class, SSCAppVersionArtifactListCommand.class, SSCAppVersionArtifactPurgeCommands.class, SSCAppVersionArtifactUploadCommand.class, diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactDownloadCommand.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactDownloadCommand.java index 5e2b516bcd..0c6d344edf 100644 --- a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactDownloadCommand.java +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactDownloadCommand.java @@ -41,7 +41,6 @@ import kong.unirest.UnirestInstance; import lombok.Getter; import picocli.CommandLine; -import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactUploadCommand.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactUploadCommand.java index c39c28af1e..79d572a7e8 100644 --- a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactUploadCommand.java +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/SSCAppVersionArtifactUploadCommand.java @@ -26,19 +26,9 @@ import java.io.File; -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.json.JsonHelper; -import com.fortify.cli.common.output.cli.cmd.unirest.IUnirestBaseRequestSupplier; -import com.fortify.cli.common.util.StringUtils; -import com.fortify.cli.ssc.appversion.cli.mixin.SSCAppVersionResolverMixin; -import com.fortify.cli.ssc.appversion.helper.SSCAppVersionDescriptor; import com.fortify.cli.ssc.output.cli.mixin.SSCOutputHelperMixins; -import com.fortify.cli.ssc.rest.SSCUrls; import io.micronaut.core.annotation.ReflectiveAccess; -import kong.unirest.HttpRequest; -import kong.unirest.HttpRequestWithBody; -import kong.unirest.UnirestInstance; import lombok.Getter; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -47,28 +37,12 @@ @ReflectiveAccess @Command(name = SSCOutputHelperMixins.Upload.CMD_NAME) -public class SSCAppVersionArtifactUploadCommand extends AbstractSSCAppVersionArtifactOutputCommand implements IUnirestBaseRequestSupplier { +public class SSCAppVersionArtifactUploadCommand extends AbstractSSCAppVersionArtifactUploadCommand { @Getter @Mixin private SSCOutputHelperMixins.Upload outputHelper; - @Mixin private SSCAppVersionResolverMixin.RequiredOption parentResolver; - @Parameters(arity="1") private String filePath; + @Getter @Parameters(arity="1") private File file; @Option(names = {"-e", "--engine-type"}) - private String engineType; - - @Override - public HttpRequest getBaseRequest(UnirestInstance unirest) { - SSCAppVersionDescriptor av = parentResolver.getAppVersionDescriptor(unirest); - HttpRequestWithBody request = unirest.post(SSCUrls.PROJECT_VERSION_ARTIFACTS(av.getVersionId())); - if ( StringUtils.isNotBlank(engineType) ) { - request.queryString("engineType", engineType); - } - JsonNode uploadResponse = request.multiPartContent() - .field("file", new File(filePath)) - .asObject(JsonNode.class).getBody(); - String artifactId = JsonHelper.evaluateJsonPath(uploadResponse, "$.data.id", String.class); - // TODO Do we actually show any scan data from the embedded scans? - return unirest.get(SSCUrls.ARTIFACT(artifactId)).queryString("embed","scans"); - } + @Getter private String engineType; @Override public boolean isSingular() { diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/SSCAppVersionArtifactImportFromCommands.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/SSCAppVersionArtifactImportFromCommands.java new file mode 100644 index 0000000000..453a8516c6 --- /dev/null +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/SSCAppVersionArtifactImportFromCommands.java @@ -0,0 +1,15 @@ +package com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt; + +import com.fortify.cli.common.cli.cmd.AbstractFortifyCLICommand; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.debricked.SSCAppVersionArtifactImportFromDebrickedCommand; + +import picocli.CommandLine.Command; + +@Command( + name = "import", + subcommands = { + SSCAppVersionArtifactImportFromDebrickedCommand.class + } +) +public class SSCAppVersionArtifactImportFromCommands extends AbstractFortifyCLICommand { +} diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedLoginOptions.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedLoginOptions.java new file mode 100644 index 0000000000..fbf138a6d5 --- /dev/null +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedLoginOptions.java @@ -0,0 +1,40 @@ +package com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.debricked; + +import com.fortify.cli.common.rest.runner.config.IUserCredentialsConfig; + +import io.micronaut.core.annotation.ReflectiveAccess; +import lombok.Getter; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Option; + +@ReflectiveAccess +public class DebrickedLoginOptions { + @ArgGroup(exclusive = false, multiplicity = "1", order = 1) + @Getter private DebrickedUrlConfigOptions urlConfigOptions = new DebrickedUrlConfigOptions(); + + @ArgGroup(exclusive = true, multiplicity = "1", order = 2) + @Getter private DebrickedAuthOptions authOptions = new DebrickedAuthOptions(); + + @ReflectiveAccess + public static class DebrickedAuthOptions { + @ArgGroup(exclusive = false, multiplicity = "1", order = 1) + @Getter private DebrickedUserCredentialOptions userCredentialsOptions; + @ArgGroup(exclusive = false, multiplicity = "1", order = 2) + @Getter private DebrickedAccessTokenCredentialOptions tokenOptions; + } + + @ReflectiveAccess + public static class DebrickedUserCredentialOptions implements IUserCredentialsConfig { + @Option(names = {"--debricked-user", "-u"}, required = true) + @Getter private String user; + + @Option(names = {"--debricked-password", "-p"}, interactive = true, echo = false, arity = "0..1", required = true) + @Getter private char[] password; + } + + @ReflectiveAccess + public static class DebrickedAccessTokenCredentialOptions { + @Option(names = {"--debricked-access-token", "-t"}, interactive = true, echo = false, arity = "0..1", required = true) + @Getter private char[] accessToken; + } +} diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedUrlConfigOptions.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedUrlConfigOptions.java new file mode 100644 index 0000000000..2fc6d36957 --- /dev/null +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/DebrickedUrlConfigOptions.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * (c) Copyright 2021 Micro Focus or one of its affiliates + * + * 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.ssc.appversion_artifact.cli.cmd.imprt.debricked; + +import com.fortify.cli.common.rest.runner.config.IUrlConfig; + +import io.micronaut.core.annotation.ReflectiveAccess; +import lombok.Getter; +import picocli.CommandLine.Option; + +@ReflectiveAccess +public class DebrickedUrlConfigOptions implements IUrlConfig { + // For now, this option is hidden as there is only the single debricked.com SaaS instance + @Option(names = {"--debricked-url"}, required = true, order=1, defaultValue = "https://debricked.com", hidden = true) + @Getter private String url; + + @Option(names = {"--insecure", "-k"}, required = false, description = "Disable SSL checks", defaultValue = "false", order=6) + @Getter private Boolean insecureModeEnabled; + + public boolean hasUrlConfig() { + return url!=null; + } +} diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/SSCAppVersionArtifactImportFromDebrickedCommand.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/SSCAppVersionArtifactImportFromDebrickedCommand.java new file mode 100644 index 0000000000..032b0821f0 --- /dev/null +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/appversion_artifact/cli/cmd/imprt/debricked/SSCAppVersionArtifactImportFromDebrickedCommand.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * (c) Copyright 2021 Micro Focus or one of its affiliates + * + * 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.ssc.appversion_artifact.cli.cmd.imprt.debricked; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.util.RawValue; +import com.fortify.cli.common.http.proxy.helper.ProxyHelper; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.rest.runner.GenericUnirestRunner; +import com.fortify.cli.common.rest.runner.config.UnirestJsonHeaderConfigurer; +import com.fortify.cli.common.rest.runner.config.UnirestUnexpectedHttpResponseConfigurer; +import com.fortify.cli.common.rest.runner.config.UnirestUrlConfigConfigurer; +import com.fortify.cli.common.util.FixInjection; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.AbstractSSCAppVersionArtifactUploadCommand; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.debricked.DebrickedLoginOptions.DebrickedAccessTokenCredentialOptions; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.debricked.DebrickedLoginOptions.DebrickedAuthOptions; +import com.fortify.cli.ssc.appversion_artifact.cli.cmd.imprt.debricked.DebrickedLoginOptions.DebrickedUserCredentialOptions; +import com.fortify.cli.ssc.output.cli.mixin.SSCOutputHelperMixins; + +import io.micronaut.core.annotation.ReflectiveAccess; +import jakarta.inject.Inject; +import kong.unirest.UnirestInstance; +import lombok.Getter; +import lombok.SneakyThrows; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +@ReflectiveAccess @FixInjection +@Command(name = SSCOutputHelperMixins.ImportFromDebricked.CMD_NAME) +public class SSCAppVersionArtifactImportFromDebrickedCommand extends AbstractSSCAppVersionArtifactUploadCommand { + @Getter @Mixin private SSCOutputHelperMixins.ImportFromDebricked outputHelper; + @Mixin private DebrickedLoginOptions debrickedLoginOptions; + @Inject private GenericUnirestRunner debrickedUnirestRunner; + + @Option(names = {"-e", "--engine-type"}, required = true, defaultValue = "DEBRICKED") + @Getter private String engineType; + + @Option(names = {"-f", "--save-sbom-as"}, required = false) + private String fileName; + + @Option(names = {"-r", "--repository"}, required = true) + private String repository; + + @Option(names = {"-b", "--branch"}, required = true) + private String branch; + + @Override + public boolean isSingular() { + return true; + } + + @Override @SneakyThrows + protected File getFile() { + File sbomFile = null; + if ( StringUtils.isNotBlank(fileName) ) { + sbomFile = new File(fileName); + } else { + sbomFile = File.createTempFile("debricked", ".json"); + sbomFile.deleteOnExit(); + } + return sbomFile; + } + + @Override + protected void preUpload(UnirestInstance unirest, File file) { + debrickedUnirestRunner.run(u->downloadSbom(u, file)); + } + + @Override + protected void postUpload(UnirestInstance unirest, File file) { + if ( StringUtils.isBlank(fileName) ) { + file.delete(); + } + } + + private Void downloadSbom(UnirestInstance debrickedUnirest, File file) { + configureDebrickedUnirest(debrickedUnirest); + String reportUuid = startSbomGeneration(debrickedUnirest); + waitSbomGeneration(debrickedUnirest, reportUuid, file); + return null; + } + + private void configureDebrickedUnirest(UnirestInstance debrickedUnirest) { + UnirestUnexpectedHttpResponseConfigurer.configure(debrickedUnirest); + DebrickedUrlConfigOptions debrickedUrlConfig = debrickedLoginOptions.getUrlConfigOptions(); + UnirestUrlConfigConfigurer.configure(debrickedUnirest, debrickedUrlConfig); + ProxyHelper.configureProxy(debrickedUnirest, "debricked", debrickedUrlConfig.getUrl()); + String debrickedJwtToken = getDebrickedJwtToken(debrickedUnirest); + UnirestJsonHeaderConfigurer.configure(debrickedUnirest); + String authHeader = String.format("Bearer %s", debrickedJwtToken); + debrickedUnirest.config().addDefaultHeader("Authorization", authHeader); + } + + private String getDebrickedJwtToken(UnirestInstance debrickedUnirest) { + DebrickedAuthOptions authOptions = debrickedLoginOptions.getAuthOptions(); + DebrickedUserCredentialOptions userCredentialsOptions = authOptions.getUserCredentialsOptions(); + DebrickedAccessTokenCredentialOptions tokenOptions = authOptions.getTokenOptions(); + if ( userCredentialsOptions!=null && StringUtils.isNotBlank(userCredentialsOptions.getUser()) ) { + return getDebrickedJwtToken(debrickedUnirest, userCredentialsOptions); + } else if ( tokenOptions!=null && tokenOptions.getAccessToken()!=null ) { + return getDebrickedJwtToken(debrickedUnirest, tokenOptions); + } else { + throw new IllegalArgumentException("Either Debricked user credentials or access token need to be specified"); + } + } + + private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedAccessTokenCredentialOptions tokenOptions) { + return debrickedUnirest.post("/api/login_refresh") + .header("Content-Type", "application/x-www-form-urlencoded") + .field("refresh_token", new String(tokenOptions.getAccessToken())) + .asObject(JsonNode.class) + .getBody() + .get("token") + .asText(); + } + + private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedUserCredentialOptions userCredentialsOptions) { + return debrickedUnirest.post("/api/login_check") + .header("Content-Type", "application/x-www-form-urlencoded") + .field("_username", userCredentialsOptions.getUser()) + .field("_password", new String(userCredentialsOptions.getPassword())) + .asObject(JsonNode.class) + .getBody() + .get("token") + .asText(); + } + + private String getRepositoryId(UnirestInstance debrickedUnirest) { + try { + Integer.parseInt(repository); + return repository; + } catch ( NumberFormatException e ) { + JsonNode data = debrickedUnirest.get("/api/1.0/open/repositories/get-repositories-names-links") + .asObject(JsonNode.class) + .getBody(); + return JsonHelper.evaluateJsonPath(data, "$[?(@.name == \""+repository+"\")].id", String.class); + } + } + + private String startSbomGeneration(UnirestInstance debrickedUnirest) { + ObjectNode body = new ObjectMapper().createObjectNode() + // TODO generate a proper ArrayNode + .putRawValue("repositoryIds", new RawValue("["+getRepositoryId(debrickedUnirest)+"]")) + .put("branch", branch) + .put("locale", "en") + .put("vulnerabilities", true) + .put("licenses", true) + .put("sendEmail", false); + return debrickedUnirest.post("/api/1.0/open/sbom/generate-cyclonedx-sbom") + .body(body) + .asObject(JsonNode.class) + .getBody() + .get("reportUuid") + .asText(); + } + + @SneakyThrows + private void waitSbomGeneration(UnirestInstance debrickedUnirest, String reportUuid, File outputFile) { + int status = 202; + while ( status==202 ) { + Thread.sleep(5000L); + status = debrickedUnirest.get("/api/1.0/open/sbom/download-generated-cyclonedx-sbom") + .queryString("reportUuid", reportUuid) + .asFile(outputFile.getAbsolutePath(), StandardCopyOption.REPLACE_EXISTING) + .getStatus(); + // Work-around for debricked not returning proper HTTP status code + status = status!=200 ? status : checkResponse(outputFile); + } + } + + @SneakyThrows + private int checkResponse(File file) { + Pattern p = Pattern.compile(".*\\\"statusCode\\\":([\\d]+).*"); + Path path = file.toPath(); + try ( Stream lines = Files.lines(path) ) { + String statusCode = lines.map(p::matcher) + .filter(Matcher::matches) + .findFirst() + .map(m->m.group(1)) + .orElse(null); + int status = statusCode==null ? 200 : Integer.parseInt(statusCode); + if ( status!=200 && status!=202 ) { + throw new IllegalStateException("Unexpected Debricked response: "+Files.readString(path)); + } + return status; + } + } +} diff --git a/fcli-ssc/src/main/java/com/fortify/cli/ssc/output/cli/mixin/SSCOutputHelperMixins.java b/fcli-ssc/src/main/java/com/fortify/cli/ssc/output/cli/mixin/SSCOutputHelperMixins.java index b96c39a5de..5428cebfc2 100644 --- a/fcli-ssc/src/main/java/com/fortify/cli/ssc/output/cli/mixin/SSCOutputHelperMixins.java +++ b/fcli-ssc/src/main/java/com/fortify/cli/ssc/output/cli/mixin/SSCOutputHelperMixins.java @@ -130,6 +130,12 @@ public final HttpRequest updateRequest(UnirestInstance unirest, HttpRequest