17: Reinvent formatting system (+ add JSON formatter)
This commit is contained in:
@@ -25,6 +25,7 @@ dependencies {
|
||||
compile "org.fusesource.jansi:jansi:${jansiVersion}"
|
||||
compile "org.apache.commons:commons-lang3:${commonsLangVersion}"
|
||||
compile "org.apache.commons:commons-text:${commonsTextVersion}"
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
@@ -8,4 +8,5 @@ ext {
|
||||
jansiVersion = '1.17.1'
|
||||
commonsLangVersion = '3.8.1'
|
||||
commonsTextVersion = '1.6'
|
||||
jacksonVersion = '2.9.8'
|
||||
}
|
||||
@@ -2,16 +2,25 @@ package com.bartek.esa;
|
||||
|
||||
import com.bartek.esa.analyser.apk.ApkAnalyser;
|
||||
import com.bartek.esa.analyser.source.SourceAnalyser;
|
||||
import com.bartek.esa.cli.model.CliArgsOptions;
|
||||
import com.bartek.esa.cli.model.object.CliArgsOptions;
|
||||
import com.bartek.esa.cli.parser.CliArgsParser;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.di.DaggerDependencyInjector;
|
||||
import com.bartek.esa.dispatcher.dispatcher.MethodDispatcher;
|
||||
import com.bartek.esa.dispatcher.model.DispatcherActions;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import com.bartek.esa.formatter.archetype.Formatter;
|
||||
import com.bartek.esa.formatter.provider.FormatterProvider;
|
||||
import io.vavr.control.Try;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EsaMain {
|
||||
@@ -39,10 +48,16 @@ public class EsaMain {
|
||||
CliArgsOptions options = cliArgsParser.parse(args);
|
||||
Set<Issue> issues = methodDispatcher.dispatchMethod(options, dispatcherActions);
|
||||
Set<Issue> filteredIssues = filterIssuesBySeverity(options, issues);
|
||||
formatterProvider.provide(options).format(filteredIssues);
|
||||
Formatter formatter = formatterProvider.provide(options);
|
||||
formatter.beforeFormat();
|
||||
String output = formatter.format(filteredIssues);
|
||||
displayOutputOrSaveToFile(options, output);
|
||||
formatter.afterFormat();
|
||||
|
||||
if(options.isStrictMode()) {
|
||||
exitWithErrorIfAnyIssueIsAnError(filteredIssues);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Issue> filterIssuesBySeverity(CliArgsOptions options, Set<Issue> issues) {
|
||||
return issues.stream()
|
||||
@@ -50,6 +65,24 @@ public class EsaMain {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private void displayOutputOrSaveToFile(CliArgsOptions options, String output) {
|
||||
Optional.ofNullable(options.getOut())
|
||||
.map(this::getWriter)
|
||||
.ifPresentOrElse(writeString(output), () -> System.out.println(output));
|
||||
}
|
||||
|
||||
private BufferedWriter getWriter(File file) {
|
||||
return Try.of(() -> new BufferedWriter(new FileWriter(file)))
|
||||
.getOrElseThrow(EsaException::new);
|
||||
}
|
||||
|
||||
private Consumer<BufferedWriter> writeString(String string) {
|
||||
return writer -> Try.run(() -> {
|
||||
writer.write(string);
|
||||
writer.close();
|
||||
}).getOrElseThrow(EsaException::new);
|
||||
}
|
||||
|
||||
private void exitWithErrorIfAnyIssueIsAnError(Set<Issue> issues) {
|
||||
if(issues.stream().anyMatch(i -> i.getSeverity().isExitWithError())) {
|
||||
System.exit(1);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.bartek.esa.cli.model.enumeration;
|
||||
|
||||
public enum OutputType {
|
||||
DEFAULT,
|
||||
COLOR,
|
||||
JSON;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.bartek.esa.cli.model;
|
||||
package com.bartek.esa.cli.model.object;
|
||||
|
||||
import com.bartek.esa.cli.model.enumeration.OutputType;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
@@ -14,9 +16,11 @@ public class CliArgsOptions {
|
||||
private String apkAuditFile;
|
||||
private Set<String> excludes;
|
||||
private Set<String> plugins;
|
||||
private boolean color;
|
||||
private OutputType outputType;
|
||||
private Set<String> severities;
|
||||
private boolean debug;
|
||||
private File out;
|
||||
private boolean strictMode;
|
||||
|
||||
public boolean isSourceAnalysis() {
|
||||
return sourceAnalysisDirectory != null;
|
||||
@@ -30,6 +34,8 @@ public class CliArgsOptions {
|
||||
return CliArgsOptions.builder()
|
||||
.excludes(emptySet())
|
||||
.plugins(emptySet())
|
||||
.severities(emptySet())
|
||||
.outputType(OutputType.DEFAULT)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.bartek.esa.cli.parser;
|
||||
|
||||
import com.bartek.esa.cli.model.CliArgsOptions;
|
||||
import com.bartek.esa.cli.model.enumeration.OutputType;
|
||||
import com.bartek.esa.cli.model.object.CliArgsOptions;
|
||||
import com.bartek.esa.cli.printer.PluginPrinter;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.commons.cli.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -19,10 +22,12 @@ public class CliArgsParser {
|
||||
private static final String EXCLUDE_OPT = "exclude";
|
||||
private static final String HELP_OPT = "help";
|
||||
private static final String PLUGINS_OPT = "plugins";
|
||||
private static final String COLOR_OPT = "color";
|
||||
private static final String FORMAT_OPT = "format";
|
||||
private static final String OUT_OPT = "out";
|
||||
private static final String SEVERITIES_OPT = "severities";
|
||||
private static final String DEBUG_OPT = "debug";
|
||||
private static final String LIST_PLUGINS_OPT = "list-plugins";
|
||||
private static final String STRICT_OPT = "strict";
|
||||
|
||||
private final PluginPrinter pluginPrinter;
|
||||
|
||||
@@ -66,12 +71,23 @@ public class CliArgsParser {
|
||||
.apkAuditFile(command.hasOption(APK_OPT) ? command.getOptionValue(APK_OPT) : null)
|
||||
.plugins(command.hasOption(PLUGINS_OPT) ? new HashSet<>(asList(command.getOptionValues(PLUGINS_OPT))) : emptySet())
|
||||
.excludes(command.hasOption(EXCLUDE_OPT) ? new HashSet<>(asList(command.getOptionValues(EXCLUDE_OPT))) : emptySet())
|
||||
.color(command.hasOption(COLOR_OPT))
|
||||
.outputType(getOutputTypeForOptions(command))
|
||||
.severities(command.hasOption(SEVERITIES_OPT) ? new HashSet<>(asList((command.getOptionValues(SEVERITIES_OPT)))) : stream(Severity.values()).map(Severity::name).collect(Collectors.toSet()))
|
||||
.debug(command.hasOption(DEBUG_OPT))
|
||||
.out(command.hasOption(OUT_OPT) ? new File(command.getOptionValue(OUT_OPT)) : null)
|
||||
.strictMode(command.hasOption(STRICT_OPT))
|
||||
.build();
|
||||
}
|
||||
|
||||
private OutputType getOutputTypeForOptions(CommandLine command) {
|
||||
if(command.hasOption(FORMAT_OPT)) {
|
||||
return Try.of(() -> OutputType.valueOf(command.getOptionValue(FORMAT_OPT).toUpperCase()))
|
||||
.getOrElse(OutputType.DEFAULT);
|
||||
}
|
||||
|
||||
return OutputType.DEFAULT;
|
||||
}
|
||||
|
||||
private void printHelp() {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("esa", prepareOptions(), true);
|
||||
@@ -84,10 +100,12 @@ public class CliArgsParser {
|
||||
options.addOption(exclude());
|
||||
options.addOption(plugins());
|
||||
options.addOption(help());
|
||||
options.addOption(color());
|
||||
options.addOption(format());
|
||||
options.addOption(severities());
|
||||
options.addOption(debug());
|
||||
options.addOption(listPlugins());
|
||||
options.addOption(out());
|
||||
options.addOption(strict());
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -134,10 +152,12 @@ public class CliArgsParser {
|
||||
.build();
|
||||
}
|
||||
|
||||
private Option color() {
|
||||
private Option format() {
|
||||
return Option.builder()
|
||||
.longOpt(COLOR_OPT)
|
||||
.desc("enable colored output")
|
||||
.longOpt(FORMAT_OPT)
|
||||
.argName("OUTPUT_TYPE")
|
||||
.numberOfArgs(1)
|
||||
.desc("select format format (available: default, color, json)")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -147,7 +167,7 @@ public class CliArgsParser {
|
||||
.longOpt(SEVERITIES_OPT)
|
||||
.argName("SEVERITY")
|
||||
.numberOfArgs(Option.UNLIMITED_VALUES)
|
||||
.desc("filter output to selected severities(available: " + severities + ")")
|
||||
.desc("filter format to selected severities(available: " + severities + ")")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -164,4 +184,20 @@ public class CliArgsParser {
|
||||
.desc("list available plugins")
|
||||
.build();
|
||||
}
|
||||
|
||||
private Option out() {
|
||||
return Option.builder()
|
||||
.longOpt(OUT_OPT)
|
||||
.argName("PATH")
|
||||
.numberOfArgs(1)
|
||||
.desc("optional output of analysis - recommended to use with non-text output format")
|
||||
.build();
|
||||
}
|
||||
|
||||
private Option strict() {
|
||||
return Option.builder()
|
||||
.longOpt(STRICT_OPT)
|
||||
.desc("enable strict mode - return code depends on analysis result (recommended to use in batch mode)")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -20,6 +21,21 @@ public class SqlInjectionPlugin extends JavaPlugin {
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("rawQuery"))
|
||||
.filter(expr -> expr.getArguments().size() >= 2)
|
||||
.filter(this::isConcatenationOrMethodCall)
|
||||
.filter(this::ifMethodIsStringFormat)
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private boolean isConcatenationOrMethodCall(MethodCallExpr expr) {
|
||||
return expr.getArguments().get(0).isBinaryExpr() || expr.getArguments().get(0).isMethodCallExpr();
|
||||
}
|
||||
|
||||
private boolean ifMethodIsStringFormat(MethodCallExpr expr) {
|
||||
if(expr.getArguments().get(0).isMethodCallExpr()) {
|
||||
return expr.getArguments().get(0).asMethodCallExpr().getName().getIdentifier().equals("format");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.bartek.esa.dispatcher.dispatcher;
|
||||
|
||||
import com.bartek.esa.cli.model.CliArgsOptions;
|
||||
import com.bartek.esa.cli.model.object.CliArgsOptions;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.dispatcher.model.DispatcherActions;
|
||||
|
||||
|
||||
@@ -5,5 +5,7 @@ import com.bartek.esa.core.model.object.Issue;
|
||||
import java.util.Set;
|
||||
|
||||
public interface Formatter {
|
||||
void format(Set<Issue> issues);
|
||||
void beforeFormat();
|
||||
String format(Set<Issue> issues);
|
||||
void afterFormat();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bartek.esa.formatter.di;
|
||||
|
||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
||||
import com.bartek.esa.formatter.formatter.ColorFormatter;
|
||||
import com.bartek.esa.formatter.formatter.JsonFormatter;
|
||||
import com.bartek.esa.formatter.formatter.SimpleFormatter;
|
||||
import com.bartek.esa.formatter.provider.FormatterProvider;
|
||||
import dagger.Module;
|
||||
@@ -21,7 +22,12 @@ public class FormatterModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) {
|
||||
return new FormatterProvider(simpleFormatter, colorFormatter);
|
||||
public JsonFormatter jsonFormatter() {
|
||||
return new JsonFormatter();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) {
|
||||
return new FormatterProvider(simpleFormatter, colorFormatter, jsonFormatter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,15 @@ public class ColorFormatter implements Formatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format(Set<Issue> issues) {
|
||||
public void beforeFormat() {
|
||||
AnsiConsole.systemInstall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Set<Issue> issues) {
|
||||
if (issues.isEmpty()) {
|
||||
Ansi noIssuesFound = ansi().fg(GREEN).a("No issues found.").reset();
|
||||
System.out.println(noIssuesFound);
|
||||
return;
|
||||
return noIssuesFound.toString();
|
||||
}
|
||||
|
||||
String format = issues.stream()
|
||||
@@ -43,8 +46,11 @@ public class ColorFormatter implements Formatter {
|
||||
.map(this::format)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
System.out.println(format.substring(0, format.length() - 2));
|
||||
System.out.println(printSummary(issues));
|
||||
return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterFormat() {
|
||||
AnsiConsole.systemUninstall();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.bartek.esa.formatter.formatter;
|
||||
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import com.bartek.esa.formatter.archetype.Formatter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.vavr.control.Try;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class JsonFormatter implements Formatter {
|
||||
|
||||
@Override
|
||||
public void beforeFormat() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Set<Issue> issues) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return Try.of(() -> objectMapper.writeValueAsString(issues))
|
||||
.getOrElseThrow(EsaException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterFormat() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,14 @@ public class SimpleFormatter implements Formatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format(Set<Issue> issues) {
|
||||
public void beforeFormat() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Set<Issue> issues) {
|
||||
if (issues.isEmpty()) {
|
||||
System.out.println("No issues found.");
|
||||
return;
|
||||
return "No issues found.";
|
||||
}
|
||||
|
||||
String format = issues.stream()
|
||||
@@ -31,8 +35,12 @@ public class SimpleFormatter implements Formatter {
|
||||
.map(this::format)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
System.out.println(format.substring(0, format.length() - 2));
|
||||
System.out.println(printSummary(issues));
|
||||
return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterFormat() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
private String format(Issue issue) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.bartek.esa.formatter.provider;
|
||||
|
||||
import com.bartek.esa.cli.model.CliArgsOptions;
|
||||
import com.bartek.esa.cli.model.object.CliArgsOptions;
|
||||
import com.bartek.esa.formatter.archetype.Formatter;
|
||||
import com.bartek.esa.formatter.formatter.ColorFormatter;
|
||||
import com.bartek.esa.formatter.formatter.JsonFormatter;
|
||||
import com.bartek.esa.formatter.formatter.SimpleFormatter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -10,14 +11,24 @@ import javax.inject.Inject;
|
||||
public class FormatterProvider {
|
||||
private final SimpleFormatter simpleFormatter;
|
||||
private final ColorFormatter colorFormatter;
|
||||
private final JsonFormatter jsonFormatter;
|
||||
|
||||
@Inject
|
||||
public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) {
|
||||
public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) {
|
||||
this.simpleFormatter = simpleFormatter;
|
||||
this.colorFormatter = colorFormatter;
|
||||
this.jsonFormatter = jsonFormatter;
|
||||
}
|
||||
|
||||
public Formatter provide(CliArgsOptions options) {
|
||||
return options.isColor() ? colorFormatter : simpleFormatter;
|
||||
switch (options.getOutputType()) {
|
||||
case COLOR:
|
||||
return colorFormatter;
|
||||
case JSON:
|
||||
return jsonFormatter;
|
||||
case DEFAULT:
|
||||
default:
|
||||
return simpleFormatter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user