Skip to content

Commit

Permalink
chore: FoD Debricked Import and Release Summary Action Update. (#562)
Browse files Browse the repository at this point in the history
feat: FoD: Debricked SBOM Export/Import (resolves #560)

fix: Update release-summary action to include OSS (resolves #561)
  • Loading branch information
kadraman authored Jul 4, 2024
1 parent c02ae4e commit aac8e10
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/
package com.fortify.cli.ssc.artifact.cli.cmd.import_debricked;
package com.fortify.cli.common.cli.cmd.import_debricked;

import java.io.File;
import java.nio.file.StandardCopyOption;
Expand All @@ -24,85 +24,35 @@
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.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.progress.helper.IProgressWriterI18n;
import com.fortify.cli.common.rest.unirest.GenericUnirestFactory;
import com.fortify.cli.common.rest.unirest.config.UnirestJsonHeaderConfigurer;
import com.fortify.cli.common.rest.unirest.config.UnirestUnexpectedHttpResponseConfigurer;
import com.fortify.cli.common.rest.unirest.config.UnirestUrlConfigConfigurer;
import com.fortify.cli.common.util.StringUtils;
import com.fortify.cli.ssc.artifact.cli.cmd.AbstractSSCArtifactUploadCommand;
import com.fortify.cli.ssc.artifact.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedAccessTokenCredentialOptions;
import com.fortify.cli.ssc.artifact.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedAuthOptions;
import com.fortify.cli.ssc.artifact.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedUserCredentialOptions;
import com.fortify.cli.common.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedAccessTokenCredentialOptions;
import com.fortify.cli.common.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedAuthOptions;
import com.fortify.cli.common.cli.cmd.import_debricked.DebrickedLoginOptions.DebrickedUserCredentialOptions;

import kong.unirest.UnirestInstance;
import lombok.Getter;
import lombok.SneakyThrows;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

@Command(name = "import-debricked")
public class SSCArtifactImportDebrickedCommand extends AbstractSSCArtifactUploadCommand {
@Mixin @Getter private OutputHelperMixins.TableNoQuery outputHelper;
@Mixin private DebrickedLoginOptions debrickedLoginOptions;

@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, IProgressWriterI18n progressWriter, File file) {
progressWriter.writeProgress("Status: Generating & downloading SBOM");
try ( var debrickedUnirest = GenericUnirestFactory.createUnirestInstance() ) {
downloadSbom(debrickedUnirest, file);
}
progressWriter.writeProgress("Status: Uploading SBOM to SSC");
}

@Override
protected void postUpload(UnirestInstance unirest, IProgressWriterI18n progressWriter, File file) {
if ( StringUtils.isBlank(fileName) ) {
file.delete();
}
progressWriter.writeProgress("Status: SBOM uploaded to SSC");
progressWriter.clearProgress();
}
public final class DebrickedHelper {

private Void downloadSbom(UnirestInstance debrickedUnirest, File file) {
private DebrickedLoginOptions debrickedLoginOptions;
private String repository;
private String branch;

public DebrickedHelper(DebrickedLoginOptions debrickedLoginOptions, String repository, String branch) {
this.debrickedLoginOptions = debrickedLoginOptions;
this.repository = repository;
this.branch = branch;
}

public final void downloadSbom(UnirestInstance debrickedUnirest, File file) {
configureDebrickedUnirest(debrickedUnirest);
String reportUuid = startSbomGeneration(debrickedUnirest);
waitSbomGeneration(debrickedUnirest, reportUuid, file);
return null;
}

private void configureDebrickedUnirest(UnirestInstance debrickedUnirest) {
public final void configureDebrickedUnirest(UnirestInstance debrickedUnirest) {
UnirestUnexpectedHttpResponseConfigurer.configure(debrickedUnirest);
DebrickedUrlConfigOptions debrickedUrlConfig = debrickedLoginOptions.getUrlConfigOptions();
UnirestUrlConfigConfigurer.configure(debrickedUnirest, debrickedUrlConfig);
Expand All @@ -113,7 +63,7 @@ private void configureDebrickedUnirest(UnirestInstance debrickedUnirest) {
debrickedUnirest.config().setDefaultHeader("Authorization", authHeader);
}

private String getDebrickedJwtToken(UnirestInstance debrickedUnirest) {
public final String getDebrickedJwtToken(UnirestInstance debrickedUnirest) {
DebrickedAuthOptions authOptions = debrickedLoginOptions.getAuthOptions();
DebrickedUserCredentialOptions userCredentialsOptions = authOptions.getUserCredentialsOptions();
DebrickedAccessTokenCredentialOptions tokenOptions = authOptions.getTokenOptions();
Expand All @@ -126,7 +76,7 @@ private String getDebrickedJwtToken(UnirestInstance debrickedUnirest) {
}
}

private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedAccessTokenCredentialOptions tokenOptions) {
public final 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()))
Expand All @@ -136,7 +86,7 @@ private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedA
.asText();
}

private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedUserCredentialOptions userCredentialsOptions) {
public final String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedUserCredentialOptions userCredentialsOptions) {
return debrickedUnirest.post("/api/login_check")
.header("Content-Type", "application/x-www-form-urlencoded")
.field("_username", userCredentialsOptions.getUser())
Expand All @@ -147,7 +97,7 @@ private String getDebrickedJwtToken(UnirestInstance debrickedUnirest, DebrickedU
.asText();
}

private String getRepositoryId(UnirestInstance debrickedUnirest) {
public final String getRepositoryId(UnirestInstance debrickedUnirest) {
try {
Integer.parseInt(repository);
return repository;
Expand All @@ -166,7 +116,7 @@ private String getRepositoryId(UnirestInstance debrickedUnirest) {
}
}

private String startSbomGeneration(UnirestInstance debrickedUnirest) {
public final String startSbomGeneration(UnirestInstance debrickedUnirest) {
ObjectNode body = new ObjectMapper().createObjectNode()
// TODO generate a proper ArrayNode
.putRawValue("repositoryIds", new RawValue("["+getRepositoryId(debrickedUnirest)+"]"))
Expand All @@ -184,7 +134,7 @@ private String startSbomGeneration(UnirestInstance debrickedUnirest) {
}

@SneakyThrows
private void waitSbomGeneration(UnirestInstance debrickedUnirest, String reportUuid, File outputFile) {
public final void waitSbomGeneration(UnirestInstance debrickedUnirest, String reportUuid, File outputFile) {
int status = 202;
while ( status==202 ) {
Thread.sleep(5000L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/
package com.fortify.cli.ssc.artifact.cli.cmd.import_debricked;
package com.fortify.cli.common.cli.cmd.import_debricked;

import com.fortify.cli.common.rest.unirest.config.IUserCredentialsConfig;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/
package com.fortify.cli.ssc.artifact.cli.cmd.import_debricked;
package com.fortify.cli.common.cli.cmd.import_debricked;

import com.fortify.cli.common.rest.cli.mixin.ConnectionConfigOptions;
import com.fortify.cli.common.rest.unirest.config.IUrlConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
// chunked uploads for SC DAST as well, so consider refactoring into a
// generic class in fcli-common.
public final class FoDFileTransferHelper {
private static final int chunkSize = FoDConstants.DEFAULT_CHUNK_SIZE;

private static int chunkSize = FoDConstants.DEFAULT_CHUNK_SIZE;
public static void setChunkSize(int chunkSize) { FoDFileTransferHelper.chunkSize = chunkSize; }

@SneakyThrows
public static final JsonNode upload(UnirestInstance unirest, HttpRequest<?> baseRequest, File f) {
Expand Down Expand Up @@ -64,7 +66,6 @@ public static final JsonNode uploadChunked(UnirestInstance unirest, HttpRequest<
throw new IllegalArgumentException("Could not read file: " + f.getPath());
}
long fileLen = f.length();

String lastBody = null;
try (var fs = new FileInputStream(f); var progressMonitor = new FoDProgressMonitor("Upload"); ) {
byte[] readByteArray = new byte[chunkSize];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@

package com.fortify.cli.fod._common.scan.cli.cmd;

import java.io.File;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.cli.mixin.CommonOptionMixins;
import com.fortify.cli.common.cli.util.CommandGroup;
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin;
import com.fortify.cli.common.progress.helper.IProgressWriterI18n;
import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin;
import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDJsonNodeOutputCommand;
import com.fortify.cli.fod._common.rest.FoDUrls;
Expand All @@ -28,26 +32,38 @@
import kong.unirest.HttpRequest;
import kong.unirest.UnirestInstance;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

@CommandGroup("*-scan-import")
public abstract class AbstractFoDScanImportCommand extends AbstractFoDJsonNodeOutputCommand implements IActionCommandResultSupplier {
@Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;
@Mixin private ProgressWriterFactoryMixin progressWriterFactory;

@Mixin private CommonOptionMixins.RequiredFile scanFileMixin;


@Option(names="--chunk-size", descriptionKey = "fcli.fod.scan.chunk-size", required = false)
private Integer chunkSize;

@Override
public final JsonNode getJsonNode(UnirestInstance unirest) {
var releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest);
var releaseId = releaseDescriptor.getReleaseId();
var importScanSessionId = getImportScanSessionId(unirest, releaseId);
HttpRequest<?> baseRequest = getBaseRequest(unirest, releaseId)
.queryString("importScanSessionId", importScanSessionId)
.queryString("fileLength", scanFileMixin.getFile().length());
FoDFileTransferHelper.uploadChunked(unirest, baseRequest, scanFileMixin.getFile());
return releaseDescriptor.asObjectNode()
.put("importScanSessionId", importScanSessionId)
.put("scanType", getScanType().name());
try ( var progressWriter = progressWriterFactory.create() ) {

var releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest);
var releaseId = releaseDescriptor.getReleaseId();
File file = scanFileMixin.getFile();
if (chunkSize != null && chunkSize > 0) FoDFileTransferHelper.setChunkSize(chunkSize);
preUpload(unirest, progressWriter, file);
var importScanSessionId = getImportScanSessionId(unirest, releaseId);
HttpRequest<?> baseRequest = getBaseRequest(unirest, releaseId)
.queryString("importScanSessionId", importScanSessionId)
.queryString("fileLength", file.length());
FoDFileTransferHelper.uploadChunked(unirest, baseRequest, file);
postUpload(unirest, progressWriter, file);
return releaseDescriptor.asObjectNode()
.put("importScanSessionId", importScanSessionId)
.put("scanType", getScanType().name());
}
}

protected abstract HttpRequest<?> getBaseRequest(UnirestInstance unirest, String releaseId);
Expand All @@ -64,6 +80,10 @@ public final boolean isSingular() {
return true;
}

//protected File getFile() { return scanFileMixin.getFile(); };
protected void preUpload(UnirestInstance unirest, IProgressWriterI18n progressWriter, File file) {}
protected void postUpload(UnirestInstance unirest, IProgressWriterI18n progressWriter, File file) {}

private static final String getImportScanSessionId(UnirestInstance unirest, String relId) {
return unirest.get(FoDUrls.RELEASE_IMPORT_SCAN_SESSION)
.routeParam("relId", relId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
FoDOssScanDownloadLatestCommand.class,
FoDOssScanGetCommand.class,
FoDOssScanImportCommand.class,
FoDOssScanImportDebrickedCommand.class,
FoDOssScanListCommand.class,
//FoDOssScanGetConfigCommand.class,
//FoDOssScanSetupCommand.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* 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.fod.oss_scan.cli.cmd;

import java.io.File;
import java.util.function.BiFunction;

import com.fortify.cli.common.cli.cmd.import_debricked.DebrickedHelper;
import com.fortify.cli.common.cli.cmd.import_debricked.DebrickedLoginOptions;
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.progress.helper.IProgressWriterI18n;
import com.fortify.cli.common.rest.unirest.GenericUnirestFactory;
import com.fortify.cli.fod._common.rest.FoDUrls;
import com.fortify.cli.fod._common.scan.cli.cmd.AbstractFoDScanImportCommand;
import com.fortify.cli.fod._common.scan.helper.FoDScanType;

import kong.unirest.HttpRequest;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

@Command(name = "import-debricked")
public class FoDOssScanImportDebrickedCommand extends AbstractFoDScanImportCommand {
@Getter @Mixin private OutputHelperMixins.Import outputHelper;
@Mixin private DebrickedLoginOptions debrickedLoginOptions;

//@Option(names = {"-f", "--save-sbom-as"}, required = false, defaultValue = "sbom.json")
//private String fileName;

@Option(names = {"-r", "--repository"}, required = true)
private String repository;

@Option(names = {"-b", "--branch"}, required = true)
private String branch;

@Option(names="--type", required = true, defaultValue = "CycloneDX")
private FoDScanImportOpenSourceType type;

@Override
protected HttpRequest<?> getBaseRequest(UnirestInstance unirest, String releaseId) {
return type.getBaseRequest(unirest, releaseId);
}

@Override
protected FoDScanType getScanType() {
return FoDScanType.OpenSource;
}

@Override
protected void preUpload(UnirestInstance unirest, IProgressWriterI18n progressWriter, File file) {
DebrickedHelper debrickedHelper = new DebrickedHelper(debrickedLoginOptions, repository, branch);
progressWriter.writeProgress("Status: Generating & downloading SBOM");
try ( var debrickedUnirest = GenericUnirestFactory.createUnirestInstance() ) {
debrickedHelper.downloadSbom(debrickedUnirest, file);
}
progressWriter.writeProgress("Status: Uploading SBOM to FoD");
}

@Override
protected void postUpload(UnirestInstance unirest, IProgressWriterI18n progressWriter, File file) {
//if ( StringUtils.isBlank(fileName) ) {
// file.delete();
//}
progressWriter.writeProgress("Status: SBOM uploaded to FoD");
progressWriter.clearProgress();
}

@RequiredArgsConstructor
public static enum FoDScanImportOpenSourceType {

CycloneDX(FoDScanImportOpenSourceType::getBaseRequestCycloneDX);

private final BiFunction<UnirestInstance, String, HttpRequest<?>> f;

public HttpRequest<?> getBaseRequest(UnirestInstance unirest, String releaseId) {
return f.apply(unirest, releaseId);
}

private static final HttpRequest<?> getBaseRequestCycloneDX(UnirestInstance unirest, String releaseId) {
return unirest.put(FoDUrls.RELEASE_IMPORT_CYCLONEDX_SBOM).routeParam("relId", releaseId);
}
}
}
Loading

0 comments on commit aac8e10

Please sign in to comment.