io.codemodder.providers.sarif.appscan.AppScanRuleSarif Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codemodder-plugin-appscan Show documentation
Show all versions of codemodder-plugin-appscan Show documentation
Plugin to enable the use of appscan in codemods
package io.codemodder.providers.sarif.appscan;
import com.contrastsecurity.sarif.*;
import io.codemodder.CodeDirectory;
import io.codemodder.RuleSarif;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.*;
/** A {@link RuleSarif} for AppScan results. */
final class AppScanRuleSarif implements RuleSarif {
private final SarifSchema210 sarif;
private final String messageText;
private final Map> resultsCache;
private final List locations;
/** A map of a AppScan SARIF "location" URIs mapped to their respective file paths. */
private final Map> artifactLocationIndices;
/**
* Creates an {@link AppScanRuleSarif} that has already done the work of mapping AppScan SARIF
* locations, which are strange combinations of class name and file path, into predictable paths.
*/
AppScanRuleSarif(
final String messageText, final SarifSchema210 sarif, final CodeDirectory codeDirectory) {
this.sarif = Objects.requireNonNull(sarif);
this.messageText = Objects.requireNonNull(messageText);
this.resultsCache = new HashMap<>();
this.locations =
sarif.getRuns().get(0).getArtifacts().stream()
.map(Artifact::getLocation)
.map(ArtifactLocation::getUri)
.map(u -> u.substring(8)) // trim the file:/// of all results
.toList();
Map> artifactLocationIndicesMap = new HashMap<>();
for (int i = 0; i < locations.size(); i++) {
final Integer index = i;
String path = locations.get(i);
path = path.replace('\\', '/');
// we have a real but partial path, now we have to find it in the repository
Optional existingRealPath;
try {
existingRealPath = codeDirectory.findFilesWithTrailingPath(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
// add to the map if we found a matching file
existingRealPath.ifPresent(
p -> artifactLocationIndicesMap.computeIfAbsent(p, k -> new HashSet<>()).add(index));
}
this.artifactLocationIndices = Map.copyOf(artifactLocationIndicesMap);
}
@Override
public List getRegionsFromResultsByRule(final Path path) {
List resultsByLocationPath = getResultsByLocationPath(path);
return resultsByLocationPath.stream()
.map(result -> result.getLocations().get(0).getPhysicalLocation().getRegion())
.toList();
}
/**
* This call receives an actual source file path, whereas the AppScan results store a reference to
* a fully qualified class name plus ".java", e.g.:
*
* file:///org/owasp/webgoat/lessons/challenges/challenge5/Assignment5.java
*/
@Override
public List getResultsByLocationPath(final Path path) {
return resultsCache.computeIfAbsent(
path,
p ->
sarif.getRuns().stream()
.flatMap(run -> run.getResults().stream())
.filter(result -> result.getMessage().getText().equals(messageText))
.filter(
result ->
artifactLocationIndices.get(path) != null
&& artifactLocationIndices
.get(path)
.contains(
result
.getLocations()
.get(0)
.getPhysicalLocation()
.getArtifactLocation()
.getIndex()))
.toList());
}
@Override
public String getDriver() {
return toolName;
}
/**
* This returns the raw SARIF. This SARIF, for Java, contains binary analysis results. These
* results may need a lot of massaging to act on.
*/
@Override
public SarifSchema210 rawDocument() {
return sarif;
}
/**
* This returns the "message[text]" field from the SARIF results. This is a human-readable value
* like "SQL Injection". We would ordinarily use this as the rule ID but this value is different
* each time we retrieve the SARIF for a given scan
*/
@Override
public String getRule() {
return messageText;
}
static final String toolName = "HCL AppScan Static Analyzer";
}