10: Create ExportedComponentsPlugin
This commit is contained in:
@@ -3,9 +3,16 @@ package com.bartek.esa.core.archetype;
|
|||||||
import com.bartek.esa.core.xml.XmlHelper;
|
import com.bartek.esa.core.xml.XmlHelper;
|
||||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
import java.io.File;
|
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 {
|
public abstract class XmlPlugin extends BasePlugin {
|
||||||
private final GlobMatcher globMatcher;
|
private final GlobMatcher globMatcher;
|
||||||
@@ -32,4 +39,21 @@ public abstract class XmlPlugin extends BasePlugin {
|
|||||||
protected Object xPath(Document xml, String expression, QName returnType) {
|
protected Object xPath(Document xml, String expression, QName returnType) {
|
||||||
return xmlHelper.xPath(xml, expression, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,4 +95,10 @@ public class PluginModule {
|
|||||||
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||||
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
|
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoSet
|
||||||
|
public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||||
|
return new ExportedComponentsPlugin(globMatcher, xmlHelper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.bartek.esa.core.xml;
|
|||||||
import com.bartek.esa.error.EsaException;
|
import com.bartek.esa.error.EsaException;
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
@@ -10,6 +12,8 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class XmlHelper {
|
public class XmlHelper {
|
||||||
|
|
||||||
@@ -28,4 +32,13 @@ public class XmlHelper {
|
|||||||
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
|
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
|
||||||
.getOrElseThrow(EsaException::new);
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,4 +86,24 @@ com.bartek.esa.core.plugin.ExternalStoragePlugin=External storage state is not c
|
|||||||
|
|
||||||
com.bartek.esa.core.plugin.SuppressWarningsPlugin=@SuppressWarnings annotation was found.\n\
|
com.bartek.esa.core.plugin.SuppressWarningsPlugin=@SuppressWarnings annotation was found.\n\
|
||||||
The @SuppressWarnings annotation might be hiding useful warnings.\n\
|
The @SuppressWarnings annotation might be hiding useful warnings.\n\
|
||||||
Consider removing it.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user