Skip to content
/ fcli Public
forked from fortify/fcli

Commit

Permalink
FoD: Implement fcli fod issue update command (resolves fortify#669)
Browse files Browse the repository at this point in the history
  • Loading branch information
kadraman committed Mar 6, 2025
1 parent d9bcb7b commit 87d87a0
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ public class FoDUrls {
public static final String REPORTS = ApiBase + "/reports";
public static final String REPORT = ApiBase + "/reports/{reportId}";
public static final String SCAN_POLLING_SUMMARY = ApiBase + "/releases/{relId}/scans/{scanId}/polling-summary";
public static final String VULNERABILITIES = ApiBase + "/releases/{relId}/vulnerabilities";
}
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,104 @@ public static AttributeTypes fromInt(int val) {
}
}

public enum VulnerabilitySeverityType {
// use same integer values as FoD uses internally
Low(1),
Medium(2),
High(3),
Critical(4),
Info(-2),
BestPractice(-1);

private final int _val;

VulnerabilitySeverityType(int val) {
this._val = val;
}

public int getValue() {
return this._val;
}

public String toString() {
switch (this._val) {
case 1:
return "Low";
case 2:
return "Medium";
case 3:
return "High";
case 4:
return "Critical";
case -1:
return "Best Practice";
case -2:
return "Info";
default:
return "Low";
}
}

public static VulnerabilitySeverityType fromInt(int val) {
switch (val) {
case 1:
return Low;
case 2:
return Medium;
case 3:
return High;
case 4:
return Critical;
case -1:
return BestPractice;
case -2:
return Info;
default:
return Info;
}
}
}

public enum DeveloperStatusType {
// no internal integer id representation
Open("Open"),
InRemediation("In Remediation"),
Remediated("Remediated"),
WillNotFix("Will Not Fix"),
ThirdPartyComponent("Third Party Component");

public final String value;

DeveloperStatusType(String value) {
this.value = value;
}

public String getValue() {
return this.value;
}
}

public enum AuditorStatusType {
// no internal integer id representation
PendingReview("Pending Review"),
RemediationRequired("Remediation Required"),
RemediationDeferred("Remediation Deferred"),
RiskMitigated("Risk Mitigated"),
// the following are used by Aviator and should not be set by a user
//Suspicious("Suspicious"),
//ProposedNotAnIssue("Proposed Not an Issue"),
RiskAccepted("Risk Accepted"),
NotAnIssue("Not an Issues");

public final String value;

AuditorStatusType(String value) {
this.value = value;
}

public String getValue() {
return this.value;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@CommandLine.Command(name = "issue",
subcommands = {
FoDIssueListCommand.class,
FoDIssueUpdateCommand.class,
}
)
//@DefaultVariablePropertyName("applicationId")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*******************************************************************************
* Copyright 2021, 2025 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

package com.fortify.cli.fod.issue.cli.cmd;

import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
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.util.FoDEnums.DeveloperStatusType;
import com.fortify.cli.fod._common.util.FoDEnums.AuditorStatusType;
import com.fortify.cli.fod._common.util.FoDEnums.VulnerabilitySeverityType;
import com.fortify.cli.fod.issue.helper.FoDBulkIssueUpdateRequest;
import com.fortify.cli.fod.issue.helper.FoDBulkIssueUpdateResponse;
import com.fortify.cli.fod.issue.helper.FoDIssueHelper;
import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin;
import com.fortify.cli.fod.release.helper.FoDReleaseDescriptor;

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

@Command(name = OutputHelperMixins.Update.CMD_NAME)
public class FoDIssueUpdateCommand extends AbstractFoDJsonNodeOutputCommand implements IActionCommandResultSupplier {
private static final Logger LOG = LoggerFactory.getLogger(FoDIssueUpdateCommand.class);
@Getter @Mixin private OutputHelperMixins.Update outputHelper;
@Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;

@Option(names = {"--user"}, required = true)
protected String user;
@Option(names = {"--dev-status"}, required = false)
protected DeveloperStatusType developerStatus;
@Option(names = {"--auditor-status"}, required = false)
protected AuditorStatusType auditorStatus;
@Option(names = {"--severity"}, required = false)
protected VulnerabilitySeverityType severity;
@Option(names = {"--comment"}, required = false)
protected String comment;
@Option(names = {"--ids"}, required = true, split=",")
protected ArrayList<String> vulnIds;

private long errorCount = 0;

@Override
public JsonNode getJsonNode(UnirestInstance unirest) {
FoDReleaseDescriptor releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest);

FoDBulkIssueUpdateRequest issueUpdateRequest = FoDBulkIssueUpdateRequest.builder()
.user(unirest, user)
.developerStatus(developerStatus.getValue())
.auditorStatus(auditorStatus.getValue())
.severity(severity.toString())
.comment(comment)
.vulnerabilityIds(vulnIds)
.build().validate();

LOG.debug("Updating issues: {}", vulnIds.toString());
FoDBulkIssueUpdateResponse resp = FoDIssueHelper.updateIssues(unirest, releaseDescriptor.getReleaseId(), issueUpdateRequest);
errorCount = resp.getResults()
.stream()
.filter(r -> r.getErrorCode() != 0)
.count();
resp.setIssueCount(resp.getResults().size());
resp.setErrorCount(errorCount);
LOG.debug("Response: {}", resp.getResults().toString());

return resp.asObjectNode().put("issueCount", resp.getResults().size()).put("errorCount", errorCount);
}

@Override
public String getActionCommandResult() {
return (errorCount == 0 ? "ISSUES_UPDATED" : "ISSUES_UPDATED_WITH_ERRORS");
}

@Override
public boolean isSingular() {
return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright 2021, 2025 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

package com.fortify.cli.fod.issue.helper;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.formkiq.graalvm.annotations.Reflectable;
import com.fortify.cli.common.exception.FcliSimpleException;
import com.fortify.cli.common.util.StringUtils;
import com.fortify.cli.fod.access_control.helper.FoDUserHelper;
import kong.unirest.UnirestInstance;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Reflectable @NoArgsConstructor @AllArgsConstructor
@Getter
@ToString
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FoDBulkIssueUpdateRequest {
private Integer userId;
private String developerStatus;
private String auditorStatus;
private String severity;
private String comment;
private ArrayList<String> vulnerabilityIds;

@JsonIgnore
public final FoDBulkIssueUpdateRequest validate(Consumer<List<String>> validationMessageConsumer) {
var messages = new ArrayList<String>();
validateRequired(messages, vulnerabilityIds, "Vulnerability Ids not specified");
if ( !messages.isEmpty() ) {
validationMessageConsumer.accept(messages);
}
return this;
}

@JsonIgnore
public final FoDBulkIssueUpdateRequest validate() {
return validate(messages->{throw new FcliSimpleException("Unable to update issues:\n\t"+String.join("\n\t", messages)); });
}

@JsonIgnore
private final void validateRequired(List<String> messages, Object obj, String message) {
if ( obj==null || (obj instanceof String && StringUtils.isBlank((String)obj)) ) {
messages.add(message);
}
}

public static class FoDBulkIssueUpdateRequestBuilder {
public FoDBulkIssueUpdateRequestBuilder user(UnirestInstance unirest, String user) {
int userId = 0;
if (user == null) return userId(null);
try {
userId = Integer.parseInt(user);
} catch (NumberFormatException nfe) {
userId = FoDUserHelper.getUserDescriptor(unirest, user, true).getUserId();
}
return userId(userId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright 2021, 2025 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

package com.fortify.cli.fod.issue.helper;

import java.util.ArrayList;
import com.formkiq.graalvm.annotations.Reflectable;
import com.fortify.cli.common.json.JsonNodeHolder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Reflectable @NoArgsConstructor
@Data @EqualsAndHashCode(callSuper = true)
public class FoDBulkIssueUpdateResponse extends JsonNodeHolder {
private ArrayList<VulnerabilityBulkUpdateResult> results;
private long issueCount;
private long errorCount;

@Reflectable @NoArgsConstructor
@Data
public static final class VulnerabilityBulkUpdateResult {
private String vulnerabilityId;
private Integer errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright 2021, 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/
package com.fortify.cli.fod.issue.helper;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.json.JsonHelper;
import com.fortify.cli.common.output.transform.fields.RenameFieldsTransformer;
import com.fortify.cli.fod._common.rest.FoDUrls;
import kong.unirest.UnirestInstance;
import lombok.Getter;

public class FoDIssueHelper {
@Getter
private static ObjectMapper objectMapper = new ObjectMapper();

public static final JsonNode transformRecord(JsonNode record) {
return new RenameFieldsTransformer(new String[]{}).transform(record);
}

public static final FoDBulkIssueUpdateResponse updateIssues(UnirestInstance unirest, String releaseId, FoDBulkIssueUpdateRequest issueUpdateRequest) {
ObjectNode body = objectMapper.valueToTree(issueUpdateRequest);
var result = unirest.post(FoDUrls.VULNERABILITIES + "/bulk-edit")
.routeParam("relId", releaseId)
.body(body).asObject(JsonNode.class).getBody();
return getResponse(result);
}

private static final FoDBulkIssueUpdateResponse getResponse(JsonNode node) {
return node==null ? null : JsonHelper.treeToValue(node, FoDBulkIssueUpdateResponse.class);
}

}
Loading

0 comments on commit 87d87a0

Please sign in to comment.