10: Create ExportedComponentsPlugin

This commit is contained in:
Bartłomiej Pluta
2019-04-10 08:52:34 +02:00
parent 888030ea4f
commit f7f0a2b2c6
5 changed files with 136 additions and 1 deletions

View File

@@ -3,9 +3,16 @@ 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;
@@ -32,4 +39,21 @@ public abstract class XmlPlugin extends BasePlugin {
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);
}
}

View File

@@ -95,4 +95,10 @@ public class PluginModule {
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
}
@Provides
@IntoSet
public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new ExportedComponentsPlugin(globMatcher, xmlHelper);
}
}

View File

@@ -0,0 +1,72 @@
package com.bartek.esa.core.plugin;
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;
import javax.inject.Inject;
import javax.xml.xpath.XPathConstants;
import java.util.Optional;
import static java.lang.String.format;
public class ExportedComponentsPlugin extends AndroidManifestPlugin {
@Inject
public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
}
@Override
protected void run(Document xml) {
findExportedComponents(xml, "activity");
findExportedComponents(xml, "service");
findExportedComponents(xml, "receiver");
findExportedProviders(xml);
}
private void findExportedComponents(Document xml, String component) {
NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET);
stream(exportedActivities)
.filter(this::isExported)
.filter(node -> doesNotHavePermission(node, "android:permission"))
.forEach(node -> addIssue(Severity.WARNING, format(".%s.NO_PERMISSION", component.toUpperCase()), null, tagString(node)));
}
private void findExportedProviders(Document xml) {
NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET);
stream(exportedProviders)
.filter(this::isExported)
.filter(node -> doesNotHavePermission(node, "android:writePermission")
|| doesNotHavePermission(node, "android:readPermission"))
.forEach(node -> addIssue(Severity.WARNING, ".PROVIDER.NO_PERMISSION", null, tagString(node)));
}
private boolean doesNotHavePermission(Node node, String permissionAttribute) {
Boolean doesHavePermission = Optional.ofNullable(node.getAttributes().getNamedItem(permissionAttribute))
.map(Node::getNodeValue)
.map(s -> !s.isEmpty())
.orElse(false);
return !doesHavePermission;
}
private boolean isExported(Node node) {
return Optional.ofNullable(node.getAttributes().getNamedItem("android:exported"))
.map(Node::getNodeValue)
.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);
}
}

View File

@@ -3,6 +3,8 @@ package com.bartek.esa.core.xml;
import com.bartek.esa.error.EsaException;
import io.vavr.control.Try;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.inject.Inject;
import javax.xml.namespace.QName;
@@ -10,6 +12,8 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.util.Arrays;
import java.util.stream.Stream;
public class XmlHelper {
@@ -28,4 +32,13 @@ public class XmlHelper {
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
.getOrElseThrow(EsaException::new);
}
public Stream<Node> stream(NodeList nodeList) {
Node[] nodes = new Node[nodeList.getLength()];
for (int i=0; i<nodeList.getLength(); ++i) {
nodes[i] = nodeList.item(i);
}
return Arrays.stream(nodes);
}
}

View File

@@ -87,3 +87,23 @@ com.bartek.esa.core.plugin.ExternalStoragePlugin=External storage state is not c
com.bartek.esa.core.plugin.SuppressWarningsPlugin=@SuppressWarnings annotation was found.\n\
The @SuppressWarnings annotation might be hiding useful warnings.\n\
Consider removing it.
com.bartek.esa.core.plugin.ExportedComponentsPlugin.ACTIVITY.NO_PERMISSION=Exported activity.\n\
The activity is exported but not protected by any permission. \n\
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.SERVICE.NO_PERMISSION=Exported service.\n\
The service is exported but not protected by any permission. \n\
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.RECEIVER.NO_PERMISSION=Exported broadcast receiver.\n\
The broadcast receiver is exported but not protected by any permission. \n\
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.PROVIDER.NO_PERMISSION=Exported content provider. Potential data leakage.\n\
The content provider is exported but not protected by any permissions. \n\
It means any malicious application could make use of data provided by the component and/or insert some new data. \n\
Consider using 'android:readPermission' and 'android:writePermission' tags and adding custom permission to protect it.