Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2021-2026 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.aviator._common.util;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import com.fortify.cli.common.exception.FcliSimpleException;

public final class AviatorIssueIdFilterUtils {
private AviatorIssueIdFilterUtils() {}

public static Set<String> normalizeIssueIds(List<String> issueIds) {
if (issueIds == null) {
return null;
}
Set<String> normalizedIssueIds = issueIds.stream()
.map(StringUtils::trimToNull)
.filter(StringUtils::isNotBlank)
.collect(LinkedHashSet::new, Set::add, Set::addAll);
if (normalizedIssueIds.isEmpty()) {
throw new FcliSimpleException("--issue-ids must contain at least one non-blank issue ID");
}
return normalizedIssueIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package com.fortify.cli.aviator.applyRemediation;

import java.util.Set;

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

Expand All @@ -28,6 +30,12 @@ public class ApplyAutoRemediationOnSource {

public static RemediationMetric applyRemediations(FprHandle fprHandle, String sourceCodeDirectory, IAviatorLogger logger)
throws AviatorSimpleException, AviatorTechnicalException {
return applyRemediations(fprHandle, sourceCodeDirectory, logger, null);
}

public static RemediationMetric applyRemediations(FprHandle fprHandle, String sourceCodeDirectory, IAviatorLogger logger,
Set<String> issueIdFilter)
throws AviatorSimpleException, AviatorTechnicalException {

LOG.info("Starting apply auto-remediation process for file: {}", fprHandle.getFprPath());

Expand All @@ -37,7 +45,7 @@ public static RemediationMetric applyRemediations(FprHandle fprHandle, String so
}
LOG.info("FPR validation successful");

RemediationProcessor remediationProcessor = new RemediationProcessor(fprHandle, sourceCodeDirectory);
RemediationProcessor remediationProcessor = new RemediationProcessor(fprHandle, sourceCodeDirectory, issueIdFilter);
return remediationProcessor.processRemediationXML();

}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* Copyright 2021-2026 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.aviator.fpr.processor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import com.fortify.cli.aviator.util.FprHandle;

class RemediationProcessorTest {
@TempDir
Path tempDir;

@Test
void testIssueIdFilterAppliesOnlyRequestedRemediations() throws Exception {
Path sourceDir = Files.createDirectory(tempDir.resolve("src"));
Path sourceFile = sourceDir.resolve("Example.java");
String originalContent = String.join("\n",
"class Example {",
" void run() {",
" oldOne();",
" oldTwo();",
" }",
"}",
"");
Files.writeString(sourceFile, originalContent, StandardCharsets.UTF_8);

String hash = TestHashUtil.sha256Base64Unix(originalContent);
Path fprPath = createFpr(remediationsXml(hash));

try (FprHandle fprHandle = new FprHandle(fprPath)) {
var processor = new RemediationProcessor(fprHandle, sourceDir.toString(), Set.of("ISSUE-2", "ISSUE-404"));
var metric = processor.processRemediationXML();

assertEquals(2, metric.totalRemediations());
assertEquals(1, metric.appliedRemediations());
assertEquals(1, metric.skippedRemediations());
assertEquals(Set.of("ISSUE-2"), metric.appliedIssueIds());
assertEquals(Set.of("Example.java"), metric.modifiedFiles());
String updatedContent = Files.readString(sourceFile, StandardCharsets.UTF_8).replace("\r\n", "\n");
assertTrue(updatedContent.contains(" oldOne();"));
assertTrue(updatedContent.contains(" newTwo();"));
assertFalse(updatedContent.contains(" newOne();"));
}
}

@Test
void testIssueIdFilterWithNoMatchesCountsRequestedIdsAsSkipped() throws Exception {
Path sourceDir = Files.createDirectory(tempDir.resolve("src-no-match"));
Path sourceFile = sourceDir.resolve("Example.java");
String originalContent = String.join("\n",
"class Example {",
" void run() {",
" oldOne();",
" }",
"}",
"");
Files.writeString(sourceFile, originalContent, StandardCharsets.UTF_8);

String hash = TestHashUtil.sha256Base64Unix(originalContent);
Path fprPath = createFpr(singleRemediationXml(hash));

try (FprHandle fprHandle = new FprHandle(fprPath)) {
var processor = new RemediationProcessor(fprHandle, sourceDir.toString(), new LinkedHashSet<>(Set.of("ISSUE-404", "ISSUE-405")));
var metric = processor.processRemediationXML();

assertEquals(2, metric.totalRemediations());
assertEquals(0, metric.appliedRemediations());
assertEquals(2, metric.skippedRemediations());
assertEquals(Set.of(), metric.appliedIssueIds());
assertEquals(Set.of(), metric.modifiedFiles());
assertEquals(originalContent, Files.readString(sourceFile, StandardCharsets.UTF_8));
}
}

@Test
void testUnfilteredPathTraversalCandidateIsSkippedWithoutAborting() throws Exception {
Path sourceDir = Files.createDirectory(tempDir.resolve("src-path-traversal"));
Path sourceFile = sourceDir.resolve("Example.java");
String originalContent = String.join("\n",
"class Example {",
" void run() {",
" oldOne();",
" }",
"}",
"");
Files.writeString(sourceFile, originalContent, StandardCharsets.UTF_8);

String hash = TestHashUtil.sha256Base64Unix(originalContent);
Path fprPath = createFpr(pathTraversalAndValidRemediationsXml(hash));

try (FprHandle fprHandle = new FprHandle(fprPath)) {
var processor = new RemediationProcessor(fprHandle, sourceDir.toString(), null);
var metric = processor.processRemediationXML();

assertEquals(2, metric.totalRemediations());
assertEquals(1, metric.appliedRemediations());
assertEquals(1, metric.skippedRemediations());
String updatedContent = Files.readString(sourceFile, StandardCharsets.UTF_8).replace("\r\n", "\n");
assertTrue(updatedContent.contains(" newOne();"));
}
}

private Path createFpr(String remediationsXml) throws IOException {
Path fprPath = tempDir.resolve("test.fpr");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(fprPath))) {
writeEntry(zipOutputStream, "audit.fvdl", "<FVDL />");
writeEntry(zipOutputStream, "remediations.xml", remediationsXml);
}
return fprPath;
}

private void writeEntry(ZipOutputStream zipOutputStream, String entryName, String content) throws IOException {
zipOutputStream.putNextEntry(new ZipEntry(entryName));
zipOutputStream.write(content.getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
}

private String remediationsXml(String hash) {
return """
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Remediations xmlns=\"xmlns://www.fortify.com/schema/remediations\">
<Remediation instanceId=\"ISSUE-1\">
<FileChanges>
<Filename>Example.java</Filename>
<Hash>%s</Hash>
<Change>
<LineFrom>3</LineFrom>
<LineTo>3</LineTo>
<Context> void run() {\n oldOne();\n oldTwo();</Context>
<OriginalCode> oldOne();</OriginalCode>
<NewCode> newOne();</NewCode>
</Change>
</FileChanges>
</Remediation>
<Remediation instanceId=\"ISSUE-2\">
<FileChanges>
<Filename>Example.java</Filename>
<Hash>%s</Hash>
<Change>
<LineFrom>4</LineFrom>
<LineTo>4</LineTo>
<Context> oldOne();\n oldTwo();\n }</Context>
<OriginalCode> oldTwo();</OriginalCode>
<NewCode> newTwo();</NewCode>
</Change>
</FileChanges>
</Remediation>
</Remediations>
""".formatted(hash, hash);
}

private String singleRemediationXml(String hash) {
return """
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Remediations xmlns=\"xmlns://www.fortify.com/schema/remediations\">
<Remediation instanceId=\"ISSUE-1\">
<FileChanges>
<Filename>Example.java</Filename>
<Hash>%s</Hash>
<Change>
<LineFrom>3</LineFrom>
<LineTo>3</LineTo>
<Context> void run() {\n oldOne();\n }</Context>
<OriginalCode> oldOne();</OriginalCode>
<NewCode> newOne();</NewCode>
</Change>
</FileChanges>
</Remediation>
</Remediations>
""".formatted(hash);
}

private String pathTraversalAndValidRemediationsXml(String hash) {
return """
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Remediations xmlns=\"xmlns://www.fortify.com/schema/remediations\">
<Remediation instanceId=\"ISSUE-TRAVERSAL\">
<FileChanges>
<Filename>../outside.java</Filename>
<Hash>%s</Hash>
<Change>
<LineFrom>3</LineFrom>
<LineTo>3</LineTo>
<Context> void run() {\n oldOne();\n }</Context>
<OriginalCode> oldOne();</OriginalCode>
<NewCode> ignored();</NewCode>
</Change>
</FileChanges>
</Remediation>
<Remediation instanceId=\"ISSUE-1\">
<FileChanges>
<Filename>Example.java</Filename>
<Hash>%s</Hash>
<Change>
<LineFrom>3</LineFrom>
<LineTo>3</LineTo>
<Context> void run() {\n oldOne();\n }</Context>
<OriginalCode> oldOne();</OriginalCode>
<NewCode> newOne();</NewCode>
</Change>
</FileChanges>
</Remediation>
</Remediations>
""".formatted(hash, hash);
}

private static final class TestHashUtil {
private static String sha256Base64Unix(String content) {
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(content.replace("\r\n", "\n").getBytes(StandardCharsets.UTF_8));
return java.util.Base64.getEncoder().encodeToString(digest);
} catch (java.security.NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
}
}
Loading
Loading