Merge branch '17-reinvent-architecture'
This commit is contained in:
@@ -6,7 +6,7 @@ plugins {
|
||||
group 'com.bartek'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 1.12
|
||||
sourceCompatibility = 1.11
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -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,24 @@ 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.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EsaMain {
|
||||
@@ -39,10 +47,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 +64,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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bartek.esa.analyser.apk;
|
||||
|
||||
import com.bartek.esa.analyser.core.Analyser;
|
||||
import com.bartek.esa.context.constructor.ContextConstructor;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.decompiler.archetype.Decompiler;
|
||||
@@ -19,8 +20,8 @@ public class ApkAnalyser extends Analyser {
|
||||
private final FileCleaner fileCleaner;
|
||||
private final GlobMatcher globMatcher;
|
||||
|
||||
public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
|
||||
super(pluginExecutor, plugins, fileProvider);
|
||||
public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) {
|
||||
super(pluginExecutor, plugins, fileProvider, contextConstructor);
|
||||
this.decompiler = decompiler;
|
||||
this.fileCleaner = fileCleaner;
|
||||
this.globMatcher = globMatcher;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.bartek.esa.analyser.core;
|
||||
|
||||
import com.bartek.esa.context.constructor.ContextConstructor;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
@@ -7,31 +9,34 @@ import com.bartek.esa.error.EsaException;
|
||||
import com.bartek.esa.file.provider.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class Analyser {
|
||||
private final PluginExecutor pluginExecutor;
|
||||
private final Set<Plugin> plugins;
|
||||
private final FileProvider fileProvider;
|
||||
private final ContextConstructor contextConstructor;
|
||||
|
||||
public Analyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
|
||||
public Analyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
|
||||
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.plugins = plugins;
|
||||
this.fileProvider = fileProvider;
|
||||
this.contextConstructor = contextConstructor;
|
||||
}
|
||||
|
||||
public Set<Issue> analyse(String source, Set<String> pluginCodes, Set<String> excludeCodes, boolean debug) {
|
||||
String newSource = prepareSources(source, debug);
|
||||
File manifest = getManifest(newSource);
|
||||
Set<File> files = getFiles(newSource);
|
||||
Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes);
|
||||
Set<File> javaSources = getJavaSources(newSource);
|
||||
Set<File> layoutFiles = getLayoutFiles(newSource);
|
||||
Context context = contextConstructor.construct(manifest, javaSources, layoutFiles);
|
||||
|
||||
Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes);
|
||||
Set<Issue> issues = pluginExecutor.executeForContext(context, selectedPlugins, debug);
|
||||
|
||||
Set<Issue> issues = pluginExecutor.executeForFiles(manifest, files, selectedPlugins, debug);
|
||||
performCleaning(newSource, debug);
|
||||
return issues;
|
||||
}
|
||||
@@ -60,14 +65,12 @@ public abstract class Analyser {
|
||||
return (File) (manifests.toArray())[0];
|
||||
}
|
||||
|
||||
private Set<File> getFiles(String source) {
|
||||
Set<File> javaFiles = fileProvider.getGlobMatchedFiles(source, getJavaGlob());
|
||||
Set<File> layoutFiles = fileProvider.getGlobMatchedFiles(source, getLayoutGlob());
|
||||
Set<File> androidManifest = Collections.singleton(getManifest(source));
|
||||
private Set<File> getJavaSources(String source) {
|
||||
return fileProvider.getGlobMatchedFiles(source, getJavaGlob());
|
||||
}
|
||||
|
||||
return Stream.of(javaFiles, androidManifest, layoutFiles)
|
||||
.flatMap(Set::stream)
|
||||
.collect(Collectors.toSet());
|
||||
private Set<File> getLayoutFiles(String source) {
|
||||
return fileProvider.getGlobMatchedFiles(source, getLayoutGlob());
|
||||
}
|
||||
|
||||
private Set<Plugin> getPlugins(Set<String> pluginCodes, Set<String> excludeCodes) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bartek.esa.analyser.di;
|
||||
|
||||
import com.bartek.esa.analyser.apk.ApkAnalyser;
|
||||
import com.bartek.esa.analyser.source.SourceAnalyser;
|
||||
import com.bartek.esa.context.constructor.ContextConstructor;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.decompiler.archetype.Decompiler;
|
||||
@@ -17,12 +18,12 @@ import java.util.Set;
|
||||
public class AnalyserModule {
|
||||
|
||||
@Provides
|
||||
public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
|
||||
return new SourceAnalyser(pluginExecutor, plugins, fileProvider);
|
||||
public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
|
||||
return new SourceAnalyser(pluginExecutor, plugins, fileProvider, contextConstructor);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
|
||||
return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher);
|
||||
public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) {
|
||||
return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher, contextConstructor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bartek.esa.analyser.source;
|
||||
|
||||
import com.bartek.esa.analyser.core.Analyser;
|
||||
import com.bartek.esa.context.constructor.ContextConstructor;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
@@ -14,8 +15,8 @@ public class SourceAnalyser extends Analyser {
|
||||
private static final String JAVA_GLOB = "**/*.java";
|
||||
private static final String LAYOUT_GLOB = "**/res/layout*/*.xml";
|
||||
|
||||
public SourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
|
||||
super(pluginExecutor, plugins, fileProvider);
|
||||
public SourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
|
||||
super(pluginExecutor, plugins, fileProvider, contextConstructor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.bartek.esa.context.constructor;
|
||||
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.Problem;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.TokenRange;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class ContextConstructor {
|
||||
private final XmlHelper xmlHelper;
|
||||
private final PackageNameMatcher packageNameMatcher;
|
||||
|
||||
@Inject
|
||||
public ContextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
public Context construct(File androidManifestFile, Set<File> javaFiles, Set<File> layoutFiles) {
|
||||
Document manifest = xmlHelper.parseXml(androidManifestFile);
|
||||
String packageName = getPackageName(manifest);
|
||||
|
||||
return Context.builder()
|
||||
.packageName(packageName)
|
||||
.minSdkVersion(getUsesSdkVersion(manifest, "android:minSdkVersion"))
|
||||
.targetSdkVersion(getUsesSdkVersion(manifest, "android:targetSdkVersion"))
|
||||
.maxSdkVersion(getUsesSdkVersion(manifest, "android:maxSdkVersion"))
|
||||
.manifest(new Source<>(androidManifestFile, manifest))
|
||||
.javaSources(parseJavaFiles(javaFiles, packageName))
|
||||
.layouts(parseLayoutFiles(layoutFiles))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getPackageName(Document androidManifest) {
|
||||
return Optional.ofNullable(xmlHelper.xPath(androidManifest, "/manifest", XPathConstants.NODE))
|
||||
.map(n -> (Node) n)
|
||||
.map(Node::getAttributes)
|
||||
.map(attr -> attr.getNamedItem("package"))
|
||||
.map(Node::getNodeValue)
|
||||
.orElseThrow(() -> new EsaException("No 'package' attribute found in manifest file. Interrupting..."));
|
||||
}
|
||||
|
||||
private Integer getUsesSdkVersion(Document manifest, String attribute) {
|
||||
return Optional.ofNullable(xmlHelper.xPath(manifest, "/manifest/uses-sdk", XPathConstants.NODE))
|
||||
.map(n -> (Node) n)
|
||||
.map(Node::getAttributes)
|
||||
.map(attr -> attr.getNamedItem(attribute))
|
||||
.map(Node::getNodeValue)
|
||||
.map(Integer::parseInt)
|
||||
.orElse(null);
|
||||
|
||||
}
|
||||
|
||||
private Set<Source<Document>> parseLayoutFiles(Set<File> layoutFiles) {
|
||||
return layoutFiles.stream()
|
||||
.map(file -> new Source<>(file, xmlHelper.parseXml(file)))
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
private Set<Source<CompilationUnit>> parseJavaFiles(Set<File> javaFiles, String packageName) {
|
||||
return javaFiles.stream()
|
||||
.filter(file -> packageNameMatcher.doesFileMatchPackageName(file, packageName))
|
||||
.map(file -> new Source<>(file, parseJava(file)))
|
||||
.filter(s -> s.getModel() != null)
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
private CompilationUnit parseJava(File javaFile) {
|
||||
try {
|
||||
return StaticJavaParser.parse(javaFile);
|
||||
} catch (ParseProblemException e) {
|
||||
printParsingErrorToStderr(e, javaFile);
|
||||
} catch (Exception e) {
|
||||
throw new EsaException(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void printParsingErrorToStderr(ParseProblemException e, File file) {
|
||||
e.getProblems().stream()
|
||||
.map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getProblemRange(p), p.getMessage()))
|
||||
.forEach(System.err::println);
|
||||
}
|
||||
|
||||
private String getProblemRange(Problem problem) {
|
||||
return problem.getLocation()
|
||||
.flatMap(TokenRange::toRange)
|
||||
.map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column))
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/bartek/esa/context/di/ContextModule.java
Normal file
15
src/main/java/com/bartek/esa/context/di/ContextModule.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.bartek.esa.context.di;
|
||||
|
||||
import com.bartek.esa.context.constructor.ContextConstructor;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import dagger.Module;
|
||||
|
||||
@Module
|
||||
public class ContextModule {
|
||||
|
||||
public ContextConstructor contextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new ContextConstructor(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
}
|
||||
25
src/main/java/com/bartek/esa/context/model/Context.java
Normal file
25
src/main/java/com/bartek/esa/context/model/Context.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.bartek.esa.context.model;
|
||||
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public class Context {
|
||||
private String packageName;
|
||||
private Integer minSdkVersion;
|
||||
private Integer targetSdkVersion;
|
||||
private Integer maxSdkVersion;
|
||||
private Source<Document> manifest;
|
||||
private Set<Source<CompilationUnit>> javaSources;
|
||||
private Set<Source<Document>> layouts;
|
||||
}
|
||||
17
src/main/java/com/bartek/esa/context/model/Source.java
Normal file
17
src/main/java/com/bartek/esa/context/model/Source.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.bartek.esa.context.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Source <M> {
|
||||
private final File file;
|
||||
private final M model;
|
||||
}
|
||||
@@ -1,20 +1,15 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class AndroidManifestPlugin extends XmlPlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
|
||||
public AndroidManifestPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.globMatcher = globMatcher;
|
||||
}
|
||||
public abstract class AndroidManifestPlugin extends BasePlugin {
|
||||
|
||||
@Override
|
||||
public boolean supports(File file) {
|
||||
return globMatcher.fileMatchesGlobPattern(file, "**/AndroidManifest.xml");
|
||||
protected void run(Context context) {
|
||||
run(context.getManifest());
|
||||
}
|
||||
|
||||
protected abstract void run(Source<Document> manifest);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,92 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import org.w3c.dom.Document;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public abstract class BasePlugin implements Plugin {
|
||||
private Set<Issue> issues = new HashSet<>();
|
||||
private Document manifest;
|
||||
private File file;
|
||||
private Set<Issue> issues;
|
||||
|
||||
@Override
|
||||
public void update(File file, Document manifest) {
|
||||
this.file = file;
|
||||
this.manifest = manifest;
|
||||
this.issues.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Issue> runForIssues() {
|
||||
run(file);
|
||||
public Set<Issue> runForIssues(Context context) {
|
||||
issues = new HashSet<>();
|
||||
run(context);
|
||||
return issues;
|
||||
}
|
||||
|
||||
protected abstract void run(File file);
|
||||
protected abstract void run(Context context);
|
||||
|
||||
protected void addIssue(Severity severity, Integer lineNumber, String line) {
|
||||
addIssue(severity, "", lineNumber, line);
|
||||
protected void addJavaIssue(Severity severity, File file, Expression expression) {
|
||||
addIssue(severity, file, getLineNumberFromExpression(expression), expression.toString());
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, Map<String, String> descriptionModel, Integer lineNumber, String line) {
|
||||
addIssue(severity, "", descriptionModel, lineNumber, line);
|
||||
private Integer getLineNumberFromExpression(Expression expression) {
|
||||
return expression.getRange().map(r -> r.begin.line).orElse(null);
|
||||
}
|
||||
|
||||
protected void addJavaIssue(Severity severity, Map<String, String> descriptionModel, File file, Expression expression) {
|
||||
addIssue(severity, "", descriptionModel, file, getLineNumberFromExpression(expression), expression.toString());
|
||||
}
|
||||
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, Integer lineNumber, String line) {
|
||||
addIssue(severity, descriptionCode, new HashMap<>(), lineNumber, line);
|
||||
protected void addJavaIssue(Severity severity, String descriptionCode, File file, Expression expression) {
|
||||
addIssue(severity, descriptionCode, new HashMap<>(), file, getLineNumberFromExpression(expression), expression.toString());
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, Integer lineNumber, String line) {
|
||||
protected void addJavaIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Expression expression) {
|
||||
addIssue(severity, descriptionCode, descriptionModel, file, getLineNumberFromExpression(expression), expression.toString());
|
||||
}
|
||||
|
||||
protected void addXmlIssue(Severity severity, File file, Node node) {
|
||||
addIssue(severity, file, null, tagString(node));
|
||||
}
|
||||
|
||||
protected void addXmlIssue(Severity severity, Map<String, String> descriptionModel, File file, Node node) {
|
||||
addIssue(severity, descriptionModel, file, null, tagString(node));
|
||||
}
|
||||
|
||||
protected void addXmlIssue(Severity severity, String descriptionCode, File file, Node node) {
|
||||
addIssue(severity, descriptionCode, file, null, tagString(node));
|
||||
}
|
||||
|
||||
protected void addXmlIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Node node) {
|
||||
addIssue(severity, descriptionCode, descriptionModel, file, null, tagString(node));
|
||||
}
|
||||
|
||||
protected String tagString(Node node) {
|
||||
Node[] attributes = new Node[node.getAttributes().getLength()];
|
||||
for (int i = 0; i < attributes.length; ++i) {
|
||||
attributes[i] = node.getAttributes().item(i);
|
||||
}
|
||||
|
||||
String attributesString = Arrays.stream(attributes)
|
||||
.map(n -> format("%s=\"%s\"", n.getNodeName(), n.getNodeValue()))
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
return format("<%s %s ...", node.getNodeName(), attributesString);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, File file, Integer lineNumber, String line) {
|
||||
addIssue(severity, "", file, lineNumber, line);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, Map<String, String> descriptionModel, File file, Integer lineNumber, String line) {
|
||||
addIssue(severity, "", descriptionModel, file, lineNumber, line);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, File file, Integer lineNumber, String line) {
|
||||
addIssue(severity, descriptionCode, new HashMap<>(), file, lineNumber, line);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Integer lineNumber, String line) {
|
||||
Issue issue = Issue.builder()
|
||||
.severity(severity)
|
||||
.issuer(this.getClass())
|
||||
@@ -54,18 +97,10 @@ public abstract class BasePlugin implements Plugin {
|
||||
.line(line)
|
||||
.build();
|
||||
|
||||
issues.add(issue);
|
||||
addIssue(issue);
|
||||
}
|
||||
|
||||
protected void addIssue(Issue issue) {
|
||||
issues.add(issue);
|
||||
}
|
||||
|
||||
protected File getOriginalFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
protected Document getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,15 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.Problem;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.TokenRange;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public abstract class JavaPlugin extends BasePlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
public JavaPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
this.globMatcher = globMatcher;
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(File file) {
|
||||
return globMatcher.fileMatchesGlobPattern(file, "**/*.java");
|
||||
protected void run(Context context) {
|
||||
context.getJavaSources().forEach(this::run);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(File file) {
|
||||
if(!isApplicationPackageFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
CompilationUnit compilationUnit = StaticJavaParser.parse(file);
|
||||
run(compilationUnit);
|
||||
} catch (ParseProblemException e) {
|
||||
printParsingErrorToStderr(e, file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void printParsingErrorToStderr(ParseProblemException e, File file) {
|
||||
e.getProblems().stream()
|
||||
.map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getRange(p), p.getMessage()))
|
||||
.forEach(System.err::println);
|
||||
}
|
||||
|
||||
private String getRange(Problem problem) {
|
||||
return problem.getLocation()
|
||||
.flatMap(TokenRange::toRange)
|
||||
.map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private boolean isApplicationPackageFile(File file) {
|
||||
Document manifest = getManifest();
|
||||
Node root = (Node) xmlHelper.xPath(manifest, "/manifest", XPathConstants.NODE);
|
||||
Node packageValue = root.getAttributes().getNamedItem("package");
|
||||
|
||||
if(packageValue == null) {
|
||||
Issue issue = Issue.builder()
|
||||
.issuer(JavaPlugin.class)
|
||||
.descriptionCode(".NO_PACKAGE")
|
||||
.descriptionModel(new HashMap<>())
|
||||
.severity(Severity.ERROR)
|
||||
.build();
|
||||
|
||||
addIssue(issue);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = packageValue.getNodeValue().replaceAll("\\.", "/");
|
||||
return globMatcher.fileMatchesGlobPattern(file, format("**/%s/**", path));
|
||||
}
|
||||
|
||||
protected Integer getLineNumberFromExpression(Expression expression) {
|
||||
return expression.getRange().map(r -> r.begin.line).orElse(null);
|
||||
}
|
||||
|
||||
public abstract void run(CompilationUnit compilationUnit);
|
||||
protected abstract void run(Source<CompilationUnit> compilationUnit);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
public interface Plugin {
|
||||
boolean supports(File file);
|
||||
void update(File file, Document manifest);
|
||||
Set<Issue> runForIssues();
|
||||
Set<Issue> runForIssues(Context context);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class ResourceLayoutPlugin extends XmlPlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
|
||||
public ResourceLayoutPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.globMatcher = globMatcher;
|
||||
}
|
||||
public abstract class ResourceLayoutPlugin extends BasePlugin {
|
||||
|
||||
@Override
|
||||
public boolean supports(File file) {
|
||||
return globMatcher.fileMatchesGlobPattern(file, "**/res/layout*/*.xml");
|
||||
protected void run(Context context) {
|
||||
context.getLayouts().forEach(this::run);
|
||||
}
|
||||
|
||||
protected abstract void run(Source<Document> layout);
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.bartek.esa.core.archetype;
|
||||
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public abstract class XmlPlugin extends BasePlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
public XmlPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
this.globMatcher = globMatcher;
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(File file) {
|
||||
return globMatcher.fileMatchesGlobPattern(file, "**/*.xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(File file) {
|
||||
Document xml = xmlHelper.parseXml(file);
|
||||
run(xml);
|
||||
}
|
||||
|
||||
protected abstract void run(Document xml);
|
||||
|
||||
protected Object xPath(Document xml, String expression, QName returnType) {
|
||||
return xmlHelper.xPath(xml, expression, returnType);
|
||||
}
|
||||
|
||||
protected Stream<Node> stream(NodeList nodeList) {
|
||||
return xmlHelper.stream(nodeList);
|
||||
}
|
||||
|
||||
protected String tagString(Node node) {
|
||||
Node[] attributes = new Node[node.getAttributes().getLength()];
|
||||
for(int i=0; i<attributes.length; ++i) {
|
||||
attributes[i] = node.getAttributes().item(i);
|
||||
}
|
||||
|
||||
String attributesString = Arrays.stream(attributes)
|
||||
.map(n -> format("%s=\"%s\"", n.getNodeName(), n.getNodeValue()))
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
return format("<%s %s ...", node.getNodeName(), attributesString);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.helper.StringConcatenationChecker;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import dagger.Module;
|
||||
@@ -13,8 +14,8 @@ import dagger.Provides;
|
||||
public class CoreModule {
|
||||
|
||||
@Provides
|
||||
public PluginExecutor pluginExecutor(XmlHelper xmlHelper) {
|
||||
return new PluginExecutor(xmlHelper);
|
||||
public PluginExecutor pluginExecutor() {
|
||||
return new PluginExecutor();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -37,6 +38,11 @@ public class CoreModule {
|
||||
return new StaticScopeHelper();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public StringConcatenationChecker stringConcatenationChecker(StaticScopeHelper staticScopeHelper) {
|
||||
return new StringConcatenationChecker(staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public ParentNodeFinder parentNodeFinder() {
|
||||
return new ParentNodeFinder();
|
||||
|
||||
@@ -3,10 +3,11 @@ package com.bartek.esa.core.di;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.helper.StringConcatenationChecker;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
import com.bartek.esa.core.plugin.*;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.ElementsIntoSet;
|
||||
@@ -26,127 +27,127 @@ public class PluginModule {
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin loggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new LoggingPlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
public Plugin loggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) {
|
||||
return new LoggingPlugin(staticScopeHelper, stringConcatenationChecker);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin debuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new DebuggablePlugin(globMatcher, xmlHelper);
|
||||
public Plugin debuggablePlugin(XmlHelper xmlHelper) {
|
||||
return new DebuggablePlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin allowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new AllowBackupPlugin(globMatcher, xmlHelper);
|
||||
public Plugin allowBackupPlugin(XmlHelper xmlHelper) {
|
||||
return new AllowBackupPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin permissionRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new PermissionsRaceConditionPlugin(globMatcher, xmlHelper);
|
||||
public Plugin permissionRaceConditionPlugin(XmlHelper xmlHelper) {
|
||||
return new PermissionsRaceConditionPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin secureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SecureRandomPlugin(globMatcher, xmlHelper);
|
||||
public Plugin secureRandomPlugin() {
|
||||
return new SecureRandomPlugin();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin implicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
return new ImplicitIntentsPlugin(globMatcher, xmlHelper, javaSyntaxRegexProvider);
|
||||
public Plugin implicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
return new ImplicitIntentsPlugin(javaSyntaxRegexProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin sharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SharedUidPlugin(globMatcher, xmlHelper);
|
||||
public Plugin sharedUidPlugin(XmlHelper xmlHelper) {
|
||||
return new SharedUidPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin usesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new UsesSdkPlugin(globMatcher, xmlHelper);
|
||||
public Plugin usesSdkPlugin(XmlHelper xmlHelper) {
|
||||
return new UsesSdkPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin cipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new CipherInstancePlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
public Plugin cipherInstancePlugin(StaticScopeHelper staticScopeHelper) {
|
||||
return new CipherInstancePlugin(staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin strictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new StrictModePlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
public Plugin strictModePlugin(StaticScopeHelper staticScopeHelper) {
|
||||
return new StrictModePlugin(staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin externalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
|
||||
return new ExternalStoragePlugin(globMatcher, xmlHelper, parentNodeFinder);
|
||||
public Plugin externalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) {
|
||||
return new ExternalStoragePlugin(parentNodeFinder, staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
|
||||
public Plugin suppressWarningsPlugin() {
|
||||
return new SuppressWarningsPlugin();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new ExportedComponentsPlugin(globMatcher, xmlHelper);
|
||||
public Plugin exportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new ExportedComponentsPlugin(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin dangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new DangerousPermissionPlugin(globMatcher, xmlHelper);
|
||||
public Plugin dangerousPermissionPlugin(XmlHelper xmlHelper) {
|
||||
return new DangerousPermissionPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin textInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new TextInputValidationPlugin(globMatcher, xmlHelper);
|
||||
public Plugin textInputValidationPlugin(XmlHelper xmlHelper) {
|
||||
return new TextInputValidationPlugin(xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin intentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new IntentFilterPlugin(globMatcher, xmlHelper);
|
||||
public Plugin intentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new IntentFilterPlugin(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin sqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SqlInjectionPlugin(globMatcher, xmlHelper);
|
||||
public Plugin sqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) {
|
||||
return new SqlInjectionPlugin( stringConcatenationChecker);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin worldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new WorldAccessPermissionsPlugin(globMatcher, xmlHelper);
|
||||
public Plugin worldAccessPermissionsPlugin() {
|
||||
return new WorldAccessPermissionsPlugin();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin orderedAndStickyBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new OrderedBroadcastPlugin(globMatcher, xmlHelper);
|
||||
public Plugin orderedAndStickyBroadcastPlugin() {
|
||||
return new OrderedBroadcastPlugin();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin webViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new WebViewPlugin(globMatcher, xmlHelper);
|
||||
public Plugin webViewPlugin() {
|
||||
return new WebViewPlugin();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin telephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new TelephonyManagerPlugin(globMatcher, xmlHelper);
|
||||
public Plugin telephonyManagerPlugin() {
|
||||
return new TelephonyManagerPlugin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,30 @@
|
||||
package com.bartek.esa.core.executor;
|
||||
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class PluginExecutor {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public PluginExecutor(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
public Set<Issue> executeForFiles(File manifest, Set<File> files, Set<Plugin> plugins, boolean debug) {
|
||||
return files.stream()
|
||||
.peek(file -> { if(debug) System.out.printf("File: %s\n", file.getAbsolutePath()); })
|
||||
.map(file -> executeForFile(manifest, file, plugins, debug))
|
||||
.flatMap(Set::stream)
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins, boolean debug) {
|
||||
Document xmlManifest = xmlHelper.parseXml(manifest);
|
||||
public Set<Issue> executeForContext(Context context, Set<Plugin> plugins, boolean debug) {
|
||||
return plugins.stream()
|
||||
.peek(plugin -> { if(debug) System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName()); })
|
||||
.peek(plugin -> plugin.update(file, xmlManifest))
|
||||
.filter(plugin -> plugin.supports(file))
|
||||
.map(Plugin::runForIssues)
|
||||
.peek(logPlugin(debug))
|
||||
.map(plugin -> plugin.runForIssues(context))
|
||||
.flatMap(Set::stream)
|
||||
.collect(toSet());
|
||||
|
||||
}
|
||||
|
||||
private Consumer<Plugin> logPlugin(boolean debug) {
|
||||
return plugin -> {
|
||||
if(debug) {
|
||||
System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
39
src/main/java/com/bartek/esa/core/helper/NodeUtil.java
Normal file
39
src/main/java/com/bartek/esa/core/helper/NodeUtil.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.bartek.esa.core.helper;
|
||||
|
||||
import com.github.javaparser.Position;
|
||||
import com.github.javaparser.ast.Node;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class NodeUtil {
|
||||
private Node first;
|
||||
|
||||
private NodeUtil(Node node) {
|
||||
this.first = node;
|
||||
}
|
||||
|
||||
public static NodeUtil is(Node node) {
|
||||
return new NodeUtil(node);
|
||||
}
|
||||
|
||||
public boolean after(Node second) {
|
||||
Position firstPosition = getPosition(first);
|
||||
Position secondPosition = getPosition(second);
|
||||
return firstPosition.isAfter(secondPosition);
|
||||
}
|
||||
|
||||
private Position getPosition(Node node) {
|
||||
return node.getRange()
|
||||
.map(r -> r.begin)
|
||||
.orElseGet(() -> {
|
||||
System.err.println(format("Cannot determine position of:\n%s\nProduced results might not be reliable."));
|
||||
return new Position(0, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean before(Node second) {
|
||||
Position firstPosition = getPosition(first);
|
||||
Position secondPosition = getPosition(second);
|
||||
return firstPosition.isBefore(secondPosition);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,12 @@ public class StaticScopeHelper {
|
||||
|
||||
public Predicate<MethodCallExpr> isFromScope(CompilationUnit compilationUnit, String methodName, String scope, String importScope) {
|
||||
return expr -> {
|
||||
if(!expr.getName().getIdentifier().matches(methodName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// is called with scope
|
||||
// Class.method()
|
||||
boolean isFromScope = expr.getScope()
|
||||
.filter(Expression::isNameExpr)
|
||||
.map(Expression::asNameExpr)
|
||||
@@ -28,6 +34,9 @@ public class StaticScopeHelper {
|
||||
.map(s -> s.equals(scope))
|
||||
.orElse(false);
|
||||
|
||||
// is from static import
|
||||
// import static a.b.Class.method
|
||||
// method()
|
||||
if(!isFromScope) {
|
||||
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
|
||||
.filter(ImportDeclaration::isStatic)
|
||||
@@ -39,6 +48,9 @@ public class StaticScopeHelper {
|
||||
.anyMatch(q -> q.equals(format("%s.%s", importScope, scope)));
|
||||
}
|
||||
|
||||
// is from import
|
||||
// import static a.b.Class.*
|
||||
// method()
|
||||
if(!isFromScope) {
|
||||
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
|
||||
.filter(ImportDeclaration::isStatic)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.bartek.esa.core.helper;
|
||||
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class StringConcatenationChecker {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public StringConcatenationChecker(StaticScopeHelper staticScopeHelper) {
|
||||
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
public boolean isStringConcatenation(CompilationUnit unit, Expression expr) {
|
||||
Predicate<MethodCallExpr> isStringFormatMethod = staticScopeHelper.isFromScope(unit, "format", "String", "java.lang");
|
||||
if(expr.isMethodCallExpr() && isStringFormatMethod.test(expr.asMethodCallExpr())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isStringConcatenation(expr);
|
||||
}
|
||||
|
||||
private boolean isStringConcatenation(Expression expr) {
|
||||
if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) {
|
||||
return isLiteralStringOrConcatenation(expr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLiteralStringOrConcatenation(Expression expr) {
|
||||
if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) {
|
||||
boolean isLeftArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getLeft());
|
||||
boolean isRightArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getRight());
|
||||
return isLeftArgumentString || isRightArgumentString;
|
||||
}
|
||||
|
||||
return expr.isStringLiteralExpr();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ public class Issue implements Comparable {
|
||||
public int compareTo(Object o) {
|
||||
Issue another = (Issue) o;
|
||||
int compByFile = file.compareTo(another.file);
|
||||
|
||||
if(compByFile != 0) {
|
||||
return compByFile;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AllowBackupPlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public AllowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public AllowBackupPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
|
||||
protected void run(Source<Document> manifest) {
|
||||
Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/application", XPathConstants.NODE);
|
||||
Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:allowBackup")).ifPresentOrElse(n -> {
|
||||
if (!n.getNodeValue().equals("false")) {
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString());
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", manifest.getFile(), null, n.toString());
|
||||
}
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", null, null));
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
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.MethodCallExpr;
|
||||
|
||||
@@ -16,19 +15,18 @@ public class CipherInstancePlugin extends JavaPlugin {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public CipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public CipherInstancePlugin(StaticScopeHelper staticScopeHelper) {
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("getInstance"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "getInstance", "Cipher", "javax.crypto"))
|
||||
.filter(staticScopeHelper.isFromScope(java.getModel(), "getInstance", "Cipher", "javax.crypto"))
|
||||
.filter(expr -> expr.getArguments().isNonEmpty())
|
||||
.filter(expr -> !isFullCipherQualifier(expr.getArguments().get(0).toString()))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.ERROR, java.getFile(), expr));
|
||||
}
|
||||
|
||||
private boolean isFullCipherQualifier(String qualifier) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -13,19 +13,20 @@ import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DangerousPermissionPlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public DangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public DangerousPermissionPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList customPermissions = (NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET);
|
||||
stream(customPermissions)
|
||||
protected void run(Source<Document> manifest) {
|
||||
NodeList customPermissions = (NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET);
|
||||
xmlHelper.stream(customPermissions)
|
||||
.filter(this::isDangerousPermission)
|
||||
.filter(this::doesNotHaveDescription)
|
||||
.forEach(permission -> addIssue(Severity.WARNING, null, tagString(permission)));
|
||||
.forEach(permission -> addXmlIssue(Severity.WARNING, manifest.getFile(), permission));
|
||||
}
|
||||
|
||||
private boolean isDangerousPermission(Node permission) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DebuggablePlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public DebuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public DebuggablePlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
|
||||
protected void run(Source<Document> manifest) {
|
||||
Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/application", XPathConstants.NODE);
|
||||
Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:debuggable")).ifPresentOrElse(n -> {
|
||||
if(!n.getNodeValue().equals("false")) {
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString());
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", manifest.getFile(),null, n.toString());
|
||||
}
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR",null, null));
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.archetype.BasePlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
@@ -15,27 +16,46 @@ import java.util.Optional;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class ExportedComponentsPlugin extends AndroidManifestPlugin {
|
||||
public class ExportedComponentsPlugin extends BasePlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
private final PackageNameMatcher packageNameMatcher;
|
||||
|
||||
@Inject
|
||||
public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public ExportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
findExportedComponents(xml, "activity");
|
||||
findExportedComponents(xml, "service");
|
||||
findExportedComponents(xml, "receiver");
|
||||
findExportedProviders(xml);
|
||||
protected void run(Context context) {
|
||||
findExportedComponents(context, "activity");
|
||||
findExportedComponents(context, "service");
|
||||
findExportedComponents(context, "receiver");
|
||||
findExportedProviders(context);
|
||||
}
|
||||
|
||||
private void findExportedComponents(Document xml, String component) {
|
||||
NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET);
|
||||
stream(exportedActivities)
|
||||
private void findExportedComponents(Context context, String component) {
|
||||
NodeList exportedActivities = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), format("/manifest/application/%s", component), XPathConstants.NODESET);
|
||||
xmlHelper.stream(exportedActivities)
|
||||
.filter(this::isExported)
|
||||
.filter(node -> doesNotHavePermission(node, "android:permission"))
|
||||
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null));
|
||||
.forEach(node -> {
|
||||
String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue();
|
||||
String canonicalName = context.getPackageName() + componentName;
|
||||
if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) {
|
||||
addIssue(Severity.WARNING, ".NO_PERMISSION.DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null);
|
||||
} else {
|
||||
addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) {
|
||||
return context.getJavaSources().stream()
|
||||
.filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName))
|
||||
.flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream())
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("getIntent"))
|
||||
.anyMatch(expr -> expr.getArguments().isEmpty());
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(Node node) {
|
||||
@@ -45,13 +65,13 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin {
|
||||
);
|
||||
}
|
||||
|
||||
private void findExportedProviders(Document xml) {
|
||||
NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET);
|
||||
stream(exportedProviders)
|
||||
private void findExportedProviders(Context context) {
|
||||
NodeList exportedProviders = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), "/manifest/application/provider", XPathConstants.NODESET);
|
||||
xmlHelper.stream(exportedProviders)
|
||||
.filter(this::isExported)
|
||||
.filter(node -> doesNotHavePermission(node, "android:writePermission")
|
||||
|| doesNotHavePermission(node, "android:readPermission"))
|
||||
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null));
|
||||
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null));
|
||||
}
|
||||
|
||||
private boolean doesNotHavePermission(Node node, String permissionAttribute) {
|
||||
@@ -68,13 +88,4 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin {
|
||||
.map(v -> v.equals("true"))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private String nodeToString(Node node) {
|
||||
String nodeName = Optional.ofNullable(node.getAttributes().getNamedItem("android:name"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(name -> format(" android:name=\"%s\"", name))
|
||||
.orElse("");
|
||||
|
||||
return format("<%s%s ...", node.getNodeName(), nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,51 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
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.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.bartek.esa.core.helper.NodeUtil.is;
|
||||
|
||||
public class ExternalStoragePlugin extends JavaPlugin {
|
||||
private final ParentNodeFinder parentNodeFinder;
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public ExternalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public ExternalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) {
|
||||
this.parentNodeFinder = parentNodeFinder;
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getExternalStorageDirectory|getExternalStoragePublicDirectory"))
|
||||
.forEach(this::findCheckingStorageStateForAccessingExternalStorage);
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageDirectory|getExternalStoragePublicDirectory", "Environment", "android.os"))
|
||||
.forEach(findCheckingStorageStateForAccessingExternalStorage(java));
|
||||
}
|
||||
|
||||
private void findCheckingStorageStateForAccessingExternalStorage(MethodCallExpr accessingToExternalStorage) {
|
||||
parentNodeFinder.findParentNode(accessingToExternalStorage, MethodDeclaration.class).ifPresent(methodDeclaration ->
|
||||
findCheckingStorageStateInMethodDeclaration(accessingToExternalStorage, methodDeclaration)
|
||||
private Consumer<MethodCallExpr> findCheckingStorageStateForAccessingExternalStorage(Source<CompilationUnit> java) {
|
||||
return accessingToExternalStorage -> parentNodeFinder
|
||||
.findParentNode(accessingToExternalStorage, MethodDeclaration.class)
|
||||
.ifPresent(methodDeclaration ->
|
||||
findCheckingStorageStateInMethodDeclaration(java, accessingToExternalStorage, methodDeclaration)
|
||||
);
|
||||
}
|
||||
|
||||
private void findCheckingStorageStateInMethodDeclaration(MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) {
|
||||
private void findCheckingStorageStateInMethodDeclaration(Source<CompilationUnit> java, MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) {
|
||||
boolean isStateBeingChecked = methodDeclaration.findAll(MethodCallExpr.class).stream()
|
||||
.anyMatch(e -> e.getName().getIdentifier().equals("getExternalStorageState"));
|
||||
.filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageState", "Environment", "android.os"))
|
||||
.anyMatch(checkingMethod -> is(accessingToExternalStorage).after(checkingMethod));
|
||||
|
||||
if (!isStateBeingChecked) {
|
||||
addIssue(Severity.WARNING, getLineNumberFromExpression(accessingToExternalStorage), accessingToExternalStorage.toString());
|
||||
addJavaIssue(Severity.WARNING, java.getFile(), accessingToExternalStorage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
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.Node;
|
||||
import com.github.javaparser.ast.NodeList;
|
||||
@@ -17,27 +16,26 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
private final JavaSyntaxRegexProvider java;
|
||||
private final JavaSyntaxRegexProvider javaSyntax;
|
||||
|
||||
@Inject
|
||||
public ImplicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.java = javaSyntaxRegexProvider;
|
||||
public ImplicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
this.javaSyntax = javaSyntaxRegexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
checkCreatingImplicitIntents(compilationUnit);
|
||||
checkCreatingPendingIntentsWithoutIntentVariable(compilationUnit);
|
||||
checkCreatingPendingIntentsWithIntentVariables(compilationUnit);
|
||||
checkCreatingPendingIntentsWithIntentsArraysVariables(compilationUnit);
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
checkCreatingImplicitIntents(java);
|
||||
checkCreatingPendingIntentsWithoutIntentVariable(java);
|
||||
checkCreatingPendingIntentsWithIntentVariables(java);
|
||||
checkCreatingPendingIntentsWithIntentsArraysVariables(java);
|
||||
}
|
||||
|
||||
// Works for:
|
||||
// Intent[] myIntents = { new Intent(...), ... }
|
||||
// getActivities(this, 0, myIntents, 0);
|
||||
private void checkCreatingPendingIntentsWithIntentsArraysVariables(CompilationUnit compilationUnit) {
|
||||
List<String> implicitIntentsArraysVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
private void checkCreatingPendingIntentsWithIntentsArraysVariables(Source<CompilationUnit> java) {
|
||||
List<String> implicitIntentsArraysVariables = java.getModel().findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.map(Node::getParentNode)
|
||||
@@ -49,31 +47,31 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
.map(VariableDeclarator::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivities"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isNameExpr())
|
||||
.filter(expr -> implicitIntentsArraysVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
|
||||
}
|
||||
|
||||
private void checkCreatingImplicitIntents(CompilationUnit compilationUnit) {
|
||||
List<String> intentVariables = getIntentVariables(compilationUnit);
|
||||
checkAllSetActionMethodInvocations(compilationUnit, intentVariables);
|
||||
checkCreatingImplicitIntentsUsingConstructor(compilationUnit);
|
||||
private void checkCreatingImplicitIntents(Source<CompilationUnit> java) {
|
||||
List<String> intentVariables = getIntentVariables(java);
|
||||
checkAllSetActionMethodInvocations(java, intentVariables);
|
||||
checkCreatingImplicitIntentsUsingConstructor(java);
|
||||
}
|
||||
|
||||
// Works for: new Intent(Intent.ABC), new Intent(ABC)
|
||||
private void checkCreatingImplicitIntentsUsingConstructor(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
private void checkCreatingImplicitIntentsUsingConstructor(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.forEach(objectCreation -> addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(objectCreation), objectCreation.toString()));
|
||||
.forEach(objectCreation -> addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), objectCreation));
|
||||
}
|
||||
|
||||
// Returns: i for: Intent i = new Intent(...)
|
||||
private List<String> getIntentVariables(CompilationUnit compilationUnit) {
|
||||
return compilationUnit.findAll(VariableDeclarationExpr.class).stream()
|
||||
private List<String> getIntentVariables(Source<CompilationUnit> java) {
|
||||
return java.getModel().findAll(VariableDeclarationExpr.class).stream()
|
||||
.filter(expr -> expr.getElementType().toString().equals("Intent"))
|
||||
.map(VariableDeclarationExpr::getVariables)
|
||||
.flatMap(NodeList::stream)
|
||||
@@ -90,7 +88,7 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
// Works for: new Intent(CONSTANT, ...)
|
||||
if (arguments.size() == 1) {
|
||||
Expression argument = arguments.get(0);
|
||||
isImplicit = java.isConstant(argument);
|
||||
isImplicit = javaSyntax.isConstant(argument);
|
||||
}
|
||||
|
||||
// Not works for: new Intent(this, ...)
|
||||
@@ -107,14 +105,14 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
// Works for: i.setAction(...)
|
||||
private void checkAllSetActionMethodInvocations(CompilationUnit compilationUnit, List<String> intentVariables) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).forEach(methodCall -> {
|
||||
private void checkAllSetActionMethodInvocations(Source<CompilationUnit> java, List<String> intentVariables) {
|
||||
java.getModel().findAll(MethodCallExpr.class).forEach(methodCall -> {
|
||||
boolean isCalledOnIntentObject = methodCall.getScope()
|
||||
.map(Expression::toString)
|
||||
.filter(intentVariables::contains)
|
||||
.isPresent();
|
||||
if(isCalledOnIntentObject && methodCall.getName().getIdentifier().equals("setAction")) {
|
||||
addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), methodCall);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -122,8 +120,8 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
// Works for:
|
||||
// Intent myIntent = new Intent(...)
|
||||
// getActivity(this, 0, myIntent, 0);
|
||||
private void checkCreatingPendingIntentsWithIntentVariables(CompilationUnit compilationUnit) {
|
||||
List<String> implicitIntentsVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
private void checkCreatingPendingIntentsWithIntentVariables(Source<CompilationUnit> java) {
|
||||
List<String> implicitIntentsVariables = java.getModel().findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.map(Node::getParentNode)
|
||||
@@ -133,30 +131,30 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
.map(VariableDeclarator::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isNameExpr())
|
||||
.filter(expr -> implicitIntentsVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
|
||||
}
|
||||
|
||||
private void checkCreatingPendingIntentsWithoutIntentVariable(CompilationUnit compilationUnit) {
|
||||
private void checkCreatingPendingIntentsWithoutIntentVariable(Source<CompilationUnit> java) {
|
||||
// Works for: getActivity(this, 0, new Intent(...), 0)
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isObjectCreationExpr())
|
||||
.filter(expr -> isCreatingImplicitIntent(expr.getArguments().get(2).asObjectCreationExpr()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
|
||||
|
||||
// Works for: getActivities(this, 0, new Intent[] { new Intent(...), ...}, 0)
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivities"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isArrayCreationExpr())
|
||||
.filter(expr -> isCreatingImplicitIntentsArray(expr.getArguments().get(2).asArrayCreationExpr()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
|
||||
}
|
||||
|
||||
private boolean isCreatingImplicitIntentsArray(ArrayCreationExpr arrayCreationExpr) {
|
||||
|
||||
@@ -1,30 +1,51 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.core.archetype.BasePlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class IntentFilterPlugin extends AndroidManifestPlugin {
|
||||
public class IntentFilterPlugin extends BasePlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
private final PackageNameMatcher packageNameMatcher;
|
||||
|
||||
@Inject
|
||||
public IntentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public IntentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList filters = xml.getElementsByTagName("intent-filter");
|
||||
stream(filters)
|
||||
protected void run(Context context) {
|
||||
NodeList filters = context.getManifest().getModel().getElementsByTagName("intent-filter");
|
||||
xmlHelper.stream(filters)
|
||||
.filter(this::isNotMainActivity)
|
||||
.filter(this::isNotExported)
|
||||
.map(Node::getParentNode)
|
||||
.forEach(n -> addIssue(Severity.INFO, getModel(n), null, null));
|
||||
.forEach(node -> {
|
||||
String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue();
|
||||
String canonicalName = context.getPackageName() + componentName;
|
||||
if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) {
|
||||
addIssue(Severity.WARNING, ".DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null);
|
||||
} else {
|
||||
addIssue(Severity.WARNING, getModel(node), context.getManifest().getFile(), null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isNotExported(Node component) {
|
||||
return !Optional.ofNullable(component.getAttributes().getNamedItem("android:exported"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(Node node) {
|
||||
@@ -35,17 +56,25 @@ public class IntentFilterPlugin extends AndroidManifestPlugin {
|
||||
}
|
||||
|
||||
private boolean isNotMainActivity(Node filter) {
|
||||
long mainActivityIntentFilters = stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
long mainActivityIntentFilters = xmlHelper.stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category|data"))
|
||||
.map(n -> n.getAttributes().getNamedItem("android:name"))
|
||||
.map(Node::getNodeValue)
|
||||
.filter(v -> v.equals("android.intent.action.MAIN") || v.equals("android.intent.category.LAUNCHER"))
|
||||
.count();
|
||||
|
||||
long currentIntentFilters = stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
long currentIntentFilters = xmlHelper.stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category|data"))
|
||||
.count();
|
||||
|
||||
return mainActivityIntentFilters != currentIntentFilters;
|
||||
}
|
||||
|
||||
private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) {
|
||||
return context.getJavaSources().stream()
|
||||
.filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName))
|
||||
.flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream())
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("getIntent"))
|
||||
.anyMatch(expr -> expr.getArguments().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.helper.StringConcatenationChecker;
|
||||
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.MethodCallExpr;
|
||||
|
||||
@@ -12,18 +12,21 @@ import javax.inject.Inject;
|
||||
|
||||
public class LoggingPlugin extends JavaPlugin {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
private final StringConcatenationChecker stringConcatenationChecker;
|
||||
|
||||
@Inject
|
||||
public LoggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public LoggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) {
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
this.stringConcatenationChecker = stringConcatenationChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("v|d|i|w|e|wtf"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "v|d|i|w|e|wtf", "Log", "android.util"))
|
||||
.forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.filter(staticScopeHelper.isFromScope(java.getModel(), "v|d|i|w|e|wtf", "Log", "android.util"))
|
||||
.filter(expr -> expr.getArguments().size() >= 2)
|
||||
.filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(1)))
|
||||
.forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class OrderedBroadcastPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public OrderedBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("sendOrderedBroadcast|sendOrderedBroadcastAsUser|sendStickyOrderedBroadcast|sendStickyOrderedBroadcastAsUser"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -15,21 +15,22 @@ import java.util.Map;
|
||||
import static java.lang.Integer.parseInt;
|
||||
|
||||
public class PermissionsRaceConditionPlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public PermissionsRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public PermissionsRaceConditionPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
boolean isAnyPermissionDefined = ((NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET)).getLength() > 0;
|
||||
protected void run(Source<Document> manifest) {
|
||||
boolean isAnyPermissionDefined = ((NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET)).getLength() > 0;
|
||||
if(isAnyPermissionDefined) {
|
||||
Node usesSdkNode = (Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE);
|
||||
Node usesSdkNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/uses-sdk", XPathConstants.NODE);
|
||||
Node minSdkVersionNode = usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion");
|
||||
int minSdkVersion = parseInt(minSdkVersionNode.getNodeValue());
|
||||
if(minSdkVersion < 21) {
|
||||
addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), null, minSdkVersionNode.toString());
|
||||
addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), manifest.getFile(), null, minSdkVersionNode.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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.ObjectCreationExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SecureRandomPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public SecureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("SecureRandom"))
|
||||
.filter(expr -> expr.getArguments().isNonEmpty())
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -12,17 +12,18 @@ import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SharedUidPlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public SharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public SharedUidPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node manifestNode = (Node) xPath(xml, "/manifest", XPathConstants.NODE);
|
||||
protected void run(Source<Document> manifest) {
|
||||
Node manifestNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest", XPathConstants.NODE);
|
||||
Optional.ofNullable(manifestNode.getAttributes().getNamedItem("android:sharedUserId")).ifPresent(node -> {
|
||||
addIssue(Severity.VULNERABILITY, null, node.toString());
|
||||
addIssue(Severity.VULNERABILITY, manifest.getFile(), null, node.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StringConcatenationChecker;
|
||||
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.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SqlInjectionPlugin extends JavaPlugin {
|
||||
private final StringConcatenationChecker stringConcatenationChecker;
|
||||
|
||||
@Inject
|
||||
public SqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public SqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) {
|
||||
this.stringConcatenationChecker = stringConcatenationChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("rawQuery"))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.filter(expr -> expr.getArguments().size() >= 2)
|
||||
.filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(0)))
|
||||
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
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.MethodCallExpr;
|
||||
|
||||
@@ -14,16 +13,15 @@ public class StrictModePlugin extends JavaPlugin {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public StrictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public StrictModePlugin(StaticScopeHelper staticScopeHelper) {
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("setThreadPolicy"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "setThreadPolicy", "StrictMode", "android.os"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.filter(staticScopeHelper.isFromScope(java.getModel(), "setThreadPolicy", "StrictMode", "android.os"))
|
||||
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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.AnnotationExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SuppressWarningsPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public SuppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(AnnotationExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(AnnotationExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("SuppressWarnings"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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.CastExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class TelephonyManagerPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public TelephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(CastExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(CastExpr.class).stream()
|
||||
.filter(expr -> expr.getType().isClassOrInterfaceType())
|
||||
.filter(expr -> expr.getType().asClassOrInterfaceType().getName().getIdentifier().equals("TelephonyManager"))
|
||||
.forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.ResourceLayoutPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -12,18 +12,19 @@ import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class TextInputValidationPlugin extends ResourceLayoutPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public TextInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public TextInputValidationPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList editTextNodes = xml.getElementsByTagName("EditText");
|
||||
stream(editTextNodes)
|
||||
protected void run(Source<Document> layout) {
|
||||
NodeList editTextNodes = layout.getModel().getElementsByTagName("EditText");
|
||||
xmlHelper.stream(editTextNodes)
|
||||
.filter(this::doesNotHaveInputType)
|
||||
.forEach(n -> addIssue(Severity.WARNING, null, tagString(n)));
|
||||
.forEach(n -> addXmlIssue(Severity.WARNING, layout.getFile(), n));
|
||||
}
|
||||
|
||||
private boolean doesNotHaveInputType(Node editText) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -13,22 +13,23 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UsesSdkPlugin extends AndroidManifestPlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
|
||||
@Inject
|
||||
public UsesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
public UsesSdkPlugin(XmlHelper xmlHelper) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Optional.ofNullable((Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> {
|
||||
protected void run(Source<Document> manifest) {
|
||||
Optional.ofNullable((Node) xmlHelper.xPath(manifest.getModel(), "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> {
|
||||
if(usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion") == null) {
|
||||
addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", null, null);
|
||||
addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", manifest.getFile(), null, null);
|
||||
}
|
||||
|
||||
Optional.ofNullable(usesSdkNode.getAttributes().getNamedItem("android:maxSdkVersion")).ifPresent(maxSdkVersion ->
|
||||
addIssue(Severity.ERROR, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()),null, maxSdkVersion.toString())
|
||||
addIssue(Severity.ERROR, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()), manifest.getFile(), null, maxSdkVersion.toString())
|
||||
);
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", null, null));
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", manifest.getFile(), null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,47 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class WebViewPlugin extends JavaPlugin {
|
||||
private static final String SETTINGS_METHODS = "addJavascriptInterface|setJavaScriptEnabled|setWebContentsDebuggingEnabled|setAllowFileAccess|setDomStorageEnabled";
|
||||
|
||||
@Inject
|
||||
public WebViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches(SETTINGS_METHODS))
|
||||
.forEach(this::issueMethod);
|
||||
.forEach(issueMethod(java));
|
||||
}
|
||||
|
||||
private void issueMethod(MethodCallExpr methodCall) {
|
||||
private Consumer<MethodCallExpr> issueMethod(Source<CompilationUnit> java) {
|
||||
return methodCall -> {
|
||||
switch (methodCall.getName().getIdentifier()) {
|
||||
case "addJavascriptInterface":
|
||||
addIssue(Severity.VULNERABILITY, ".JS_INTERFACE", getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
addJavaIssue(Severity.VULNERABILITY, ".JS_INTERFACE", java.getFile(), methodCall);
|
||||
break;
|
||||
case "setJavaScriptEnabled":
|
||||
issueSettingsMethod(methodCall, ".JS_ENABLED");
|
||||
issueSettingsMethod(java, methodCall, ".JS_ENABLED");
|
||||
break;
|
||||
case "setWebContentsDebuggingEnabled":
|
||||
issueSettingsMethod(methodCall, ".DEBUGGING_ENABLED");
|
||||
issueSettingsMethod(java, methodCall, ".DEBUGGING_ENABLED");
|
||||
break;
|
||||
case "setAllowFileAccess":
|
||||
issueSettingsMethod(methodCall, ".ALLOW_FILE_ACCESS");
|
||||
issueSettingsMethod(java, methodCall, ".ALLOW_FILE_ACCESS");
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void issueSettingsMethod(MethodCallExpr methodCall, String descriptionCode) {
|
||||
private void issueSettingsMethod(Source<CompilationUnit> java, MethodCallExpr methodCall, String descriptionCode) {
|
||||
Expression firstArg = methodCall.getArguments().get(0);
|
||||
if (firstArg.isBooleanLiteralExpr() && firstArg.asBooleanLiteralExpr().getValue()) {
|
||||
addIssue(Severity.WARNING, descriptionCode, getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
addJavaIssue(Severity.WARNING, descriptionCode, java.getFile(), methodCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
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.FieldAccessExpr;
|
||||
import com.github.javaparser.ast.expr.NameExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
public class WorldAccessPermissionsPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public WorldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(NameExpr.class).stream()
|
||||
public void run(Source<CompilationUnit> java) {
|
||||
java.getModel().findAll(NameExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)"))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr));
|
||||
|
||||
compilationUnit.findAll(FieldAccessExpr.class).stream()
|
||||
java.getModel().findAll(FieldAccessExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)"))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString()));
|
||||
.forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr));
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(NameExpr expression) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bartek.esa.di;
|
||||
import com.bartek.esa.EsaMain;
|
||||
import com.bartek.esa.analyser.di.AnalyserModule;
|
||||
import com.bartek.esa.cli.di.CliModule;
|
||||
import com.bartek.esa.context.di.ContextModule;
|
||||
import com.bartek.esa.core.di.CoreModule;
|
||||
import com.bartek.esa.core.di.PluginModule;
|
||||
import com.bartek.esa.decompiler.di.DecompilerModule;
|
||||
@@ -19,7 +20,8 @@ import dagger.Component;
|
||||
CoreModule.class,
|
||||
PluginModule.class,
|
||||
AnalyserModule.class,
|
||||
FormatterModule.class
|
||||
FormatterModule.class,
|
||||
ContextModule.class
|
||||
})
|
||||
public interface DependencyInjector {
|
||||
EsaMain esa();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bartek.esa.file.di;
|
||||
|
||||
import com.bartek.esa.file.cleaner.FileCleaner;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.bartek.esa.file.provider.FileContentProvider;
|
||||
import com.bartek.esa.file.provider.FileProvider;
|
||||
import com.bartek.esa.file.zip.ZipTool;
|
||||
@@ -35,4 +36,9 @@ public class FileModule {
|
||||
public FileCleaner fileCleaner() {
|
||||
return new FileCleaner();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public PackageNameMatcher packageNameMatcher() {
|
||||
return new PackageNameMatcher();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.bartek.esa.file.matcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
|
||||
public class PackageNameMatcher {
|
||||
|
||||
@Inject
|
||||
public PackageNameMatcher() {
|
||||
|
||||
}
|
||||
|
||||
public boolean doesFileMatchPackageName(File file, String packageName) {
|
||||
return file.getAbsolutePath().replaceAll(File.separator, ".").contains(packageName);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,13 @@ com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION=Exported ${com
|
||||
It means any malicious application could make use of the component. \n\
|
||||
Consider using 'android:permission' tag and adding custom permission to protect it.
|
||||
|
||||
com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION.DATA_USAGE=Exported ${componentType} makes use of data of incoming intents.\n\
|
||||
The ${componentType} with name '${componentName}' is exported but not protected by any permission. \n\
|
||||
It probably does also process a data from incoming intent. \n\
|
||||
It means any malicious application could make use of the component or inject a fake (potentially malicious) data to it. \n\
|
||||
Consider using 'android:permission' tag and adding custom permission to protect it. \n\
|
||||
Also make sure, that intent is correctly validated before processing it.
|
||||
|
||||
com.bartek.esa.core.plugin.DangerousPermissionPlugin=Custom permission without description.\n\
|
||||
Custom permission with 'dangerous' protection level was found and it doesn't have any description.\n\
|
||||
As long as the permission requires user attention, he should have provided a meaningful description about\n\
|
||||
@@ -103,12 +110,22 @@ com.bartek.esa.core.plugin.TextInputValidationPlugin=Input type is no selected.\
|
||||
Consider associating a input type with this view.\n\
|
||||
For example: <EditText android:inputType="number" ...
|
||||
|
||||
com.bartek.esa.core.plugin.IntentFilterPlugin=Implemented intent filter.\n\
|
||||
The ${componentType} with name '${componentName}' does have a intent filter declared. \n\
|
||||
com.bartek.esa.core.plugin.IntentFilterPlugin=Implemented intent filter inside private component.\n\
|
||||
The non-exported ${componentType} with name '${componentName}' does have a intent filter declared. \n\
|
||||
It means, that the component is implicitly exposed to public.\n\
|
||||
Consider removing intent filter.\n\
|
||||
Consider removing intent filter or set it explicitely to be exported using following attribute:\n\
|
||||
android:exported="true". \n\
|
||||
Also be aware, that intent filter is not a security tool. It can be easily omitted.
|
||||
|
||||
com.bartek.esa.core.plugin.IntentFilterPlugin.DATA_USAGE=Implemented intent filter inside private component making use of incoming data.\n\
|
||||
The non-exported ${componentType} with name '${componentName}' does have a intent filter declared \n\
|
||||
and also does make use of incoming intent data. \n\
|
||||
It means, that the component is implicitly exposed to public and can be spoofed with fake data.\n\
|
||||
Consider removing intent filter or set it explicitely to be exported using following attribute:\n\
|
||||
android:exported="true". \n\
|
||||
Be aware, that intent filter is not a security tool. It can be easily omitted. \n\
|
||||
Also make sure, that data is correctly validated before taking advantage of it.
|
||||
|
||||
com.bartek.esa.core.plugin.SqlInjectionPlugin='rawQuery' method detected. Potential SQL injection attack.\n\
|
||||
'rawQuery' method should be avoided because of possibility to inject SQL code.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user