为了方便快捷的进行第三方依赖的权限收集,开发了此插件来简化繁琐的手机过程。
-
通过查找
app/build/outputs/logs
下面的日志文件 -
通过直接在
android studio
下面点开依赖查看安全人员无法介入开发,只能通过与开发人员沟通获取
-
使用gradle进行自动扫描
方便快捷,开发人员一次配置后无需其他复杂流程,即可通过命令行产生相关csv文件,简化了权限收集带来的烦恼。
网上没有相关先例可以参考,
gradle
使用的是groovy
,开发难度比较大,需要开发将相关gradle
插件配置到项目中才能进行扫描。
Android的编译使用的是gradle
,项目开发中的众多第三方依赖文件都可以通过gradle获取到:
那么可以尝试开发插件来得到第三方依赖文件的文件地址,通过解析文件中的xml文件,读取uses-permission
信息来获取第三方依赖的权限申请信息。
代码:
// 注册一个task
project.task(SAVE_PERMISSIONS) {
doFirst {
// 配置权限信息保存地址
String saveFilePath = project.buildDir.getAbsolutePath() + "/permission_collection.csv"
println('persmission file save path: ' + saveFilePath)
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(saveFilePath))
// 获取Android编译配置
project.extensions.getByName('android').applicationVariants.all { variant ->
def name = "${variant.name}CompileClasspath"
println(name + " dependencies")
Configuration configuration = project.configurations.getByName(name)
// 获取到依赖文件
configuration.getIncoming().files.each { dependency ->
if (dependency.getName().endsWith(".aar")) {
println("dependency: " + dependency.getAbsolutePath())
// 解析aar文件
ZipFile zipFile = new ZipFile(dependency)
Enumeration<?> entries = zipFile.entries()
String folder = System.getProperty("java.io.tmpdir");
File cache = new File(folder, 'xmlcache')
if (!cache.isDirectory() || !cache.exists()) {
cache.mkdir()
}
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement()
// 获取xml文件
if (entry.getName().equalsIgnoreCase('AndroidManifest.xml')) {
File xmlFile = new File(cache, entry.getName())
if (xmlFile.exists()) {
xmlFile.delete()
}
InputStream is = zipFile.getInputStream(entry)
FileOutputStream fos = new FileOutputStream(xmlFile)
int len;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
buf = new byte[1024]
}
// 关流顺序,先打开的后关闭
fos.close()
is.close()
List<String> permissionList = new ArrayList<>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document d = builder.parse(xmlFile);
NodeList manifestNodeList = d.getElementsByTagName("manifest");
for (int i = 0; i < manifestNodeList.getLength(); i++) {
Node sonNode = manifestNodeList.item(i);
NodeList grandSonNodeList = sonNode.getChildNodes();
for (int j = 0; j < grandSonNodeList.getLength(); j++) {
Node grandSonNode = grandSonNodeList.item(j);
if (grandSonNode.getNodeType() == Node.ELEMENT_NODE) {
// 实际测试中发现,会出现uses-permission-sdk等字段
if (grandSonNode.getNodeName().toLowerCase().contains("uses-permission")) {
Element en = (Element) grandSonNode;
String value = en.getAttribute("android:name");
permissionList.add(value)
System.out.println(grandSonNode.getNodeName() + ": " + value);
}
}
}
}
} catch (Exception e) {
println('error msg: ' + e.getMessage())
}
//写入已经打开的文件
if (permissionList.size() != 0) {
bufferedWriter.write(dependency.getAbsolutePath())
bufferedWriter.write(',')
for (String permission : permissionList) {
bufferedWriter.write(permission)
bufferedWriter.write(',')
}
bufferedWriter.write('\n')
}
xmlFile.delete()
}
}
}
}
}
bufferedWriter.close()
}
}
-
解压zip文件到某个地址,这里以
D:\Downloads\repo
为例,修改项目根目录下的build.gradle
buildscript { repositories { .... maven{ url uri('D:/Downloads/repo') # 配置插件地址 } ..... } dependencies { ...... classpath "com.demon.plugin:plugin:1.0.0" ...... } }
-
修改app下面的
build.gradle
apply plugin: 'com.demon.plugin'
-
gradle
同步结束后,打开Android studio
的Terminal
gradlew -q save
会在app的build中生成扫描结果
为了简化配置,更新代码与配置
# 确保你的项目是可编译的,只需要在项目app的build.gradle末尾添加如下代码,然后编译dubug版app,编译完成后,在app/build下面会产生文件输出
# 也可使用命令 gradlew -q mergeDebugResources
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
......
......
gradle.taskGraph.beforeTask { task ->
if (task.name.contains('mergeDebugResources')) {
println('mergeDebugResources info: ' + task)
if (task != null) {
if (!project.buildDir.exists()) {
project.buildDir.mkdir()
}
String saveFilePath = project.buildDir.getAbsolutePath() + "/permission_collection.csv"
println('persmission file save path: ' + saveFilePath)
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(saveFilePath))
project.extensions.getByName('android').applicationVariants.all { variant ->
def name = "${variant.name}CompileClasspath"
println(name + " dependencies")
org.gradle.api.artifacts.Configuration configuration = project.configurations.getByName(name)
configuration.getIncoming().files.each { dependency ->
if (dependency.getName().endsWith(".aar")) {
println("dependency: " + dependency.getAbsolutePath())
ZipFile zipFile = new ZipFile(dependency)
Enumeration<?> entries = zipFile.entries()
String folder = System.getProperty("java.io.tmpdir");
File cache = new File(folder, 'xmlcache')
if (!cache.isDirectory() || !cache.exists()) {
cache.mkdir()
}
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement()
if (entry.getName().equalsIgnoreCase('AndroidManifest.xml')) {
File xmlFile = new File(cache, entry.getName())
if (xmlFile.exists()) {
xmlFile.delete()
}
InputStream is = zipFile.getInputStream(entry)
FileOutputStream fos = new FileOutputStream(xmlFile)
int len;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
buf = new byte[1024]
}
// 关流顺序,先打开的后关闭
fos.close()
is.close()
List<String> permissionList = new ArrayList<>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document d = builder.parse(xmlFile);
NodeList manifestNodeList = d.getElementsByTagName("manifest");
for (int i = 0; i < manifestNodeList.getLength(); i++) {
Node sonNode = manifestNodeList.item(i);
NodeList grandSonNodeList = sonNode.getChildNodes();
for (int j = 0; j < grandSonNodeList.getLength(); j++) {
Node grandSonNode = grandSonNodeList.item(j);
if (grandSonNode.getNodeType() == Node.ELEMENT_NODE) {
if (grandSonNode.getNodeName().toLowerCase().contains("uses-permission")) {
Element en = (Element) grandSonNode;
String value = en.getAttribute("android:name");
permissionList.add(value)
System.out.println(grandSonNode.getNodeName() + ": " + value);
}
}
}
}
} catch (Exception e) {
println('error msg: ' + e.getMessage())
}
//写入已经打开的文件
if (permissionList.size() != 0) {
bufferedWriter.write(dependency.getAbsolutePath())
bufferedWriter.write(',')
for (String permission : permissionList) {
bufferedWriter.write(permission)
bufferedWriter.write(',')
}
bufferedWriter.write('\n')
}
xmlFile.delete()
}
}
}
}
}
bufferedWriter.close()
}
}
}
https://www.jianshu.com/p/b1680afc80c7
https://www.jianshu.com/p/bceae568cce6
https://developer.android.com/studio/build/manifest-merge
https://github.com/wequick/Small/tree/master/Android/DevSample/buildSrc
https://blog.csdn.net/xfhy_/article/details/107840592
https://www.jianshu.com/p/b567f0ffec4e
https://github.com/2BAB/Seal/tree/1e1dfa63181eb51c8d424805f3955d4b47cb7b87
https://www.githang.com/gradledoc/userguide/dependency_management.html