如何管控清单文件中的权限

1,042 阅读4分钟

前言

为什么要做权限管控呢?因为现在的隐私合规越来越严格,对于敏感权限的使用必须在 隐私协议文档中注明权限使用的目的,甚至,在公司层面中,这类权限就不允许申请。 由于我们的项目采用组件化开发,有的小伙伴可能会因为一些技术方案需要申请一些敏感权限,也就 '自作主张'直接在自己的模块声明权限,然后直接进行了需求开发与自测,到最后快上线时被安全部门通知该权限为敏感权限不允许申请,导致需求无法上车被延期。 我们需要将权限统一管理,对于敏感权限的申请必须向上报备,并且,在小伙伴引入敏感权限开发时就能立马报错,我们需要在前期就遏制住 不友好的技术方案,避免因为需求紧急而开绿色通道。

方案

方案一

动态修改合并后的清单文件,遍历 uses-permission 节点,如果遇到不在配置内的权限,则将该节点移除,清理完之后再将最终结果回写,或是在此处直接抛出异常,将不匹配的权限打印出来提示开发人员。 优点:

  • 编译打包时自动解析,并且可以很清晰的提示开发人员

缺点:

  • 由于配置权限闭源在插件内,无法查看当前有哪些权限不符合要求,只有编译失败时才会有提示

方案二

将插件中配置的权限输出一个临时清单文件,并将该清单文件通过 sourceSet.manifest 引入参与编译,利用资源合并规则将敏感权限自动删除,如何配置删除元素可以查看官方文档的 remove 规则 优点:

  • 可以通过临时文件来查看哪些权限是敏感权限,哪些权限会保留,哪些权限会被移除

缺点:

  • 当组件模块申明的敏感权限被主工程的清单文件合并删除时无法提示,只能运行时才会表现出想申请的权限在权限设置里不存在

两种方案都能实现权限管控效果,主要看大家自己的选择,本文简要讲下 方案二 的实现

实现

1、获取主模块的清单文件,并将清单文件中的权限声明移除,避免小伙伴在主模块的清单文件中声明敏感权限运行项目

// 1、获取 main 下的清单文件,如果找不到,则手动指定清单文件
val mainAndroidManifest = project.extensions.getByType(AppExtension::class.java)
    .sourceSets.find { it.name == "main" }?.manifest?.srcFile
?: File(project.projectDir, "src/main/AndroidManifest.xml")

// 2、读取主工程下清单文件的权限,并从主工程中删除,避免有小伙伴在该文件中提交敏感权限
val parentNode = XmlParser(false, false).parse(mainAndroidManifest)
val childrenNode = parentNode.children()
val permissionNodes = childrenNode.map { it as Node }.filter { it.name() == "uses-permission" }.toList()
if (permissionNodes.isNotEmpty()) {
    // 3、移除所有权限
    childrenNode.removeAll(permissionNodes)
    val xmlText = XmlUtil.serialize(parentNode)
    // 4、回写主工程的清单文件
    mainAndroidManifest.writeText(xmlText)
}

2、生成临时清单文件,将插件中的权限回写进临时清单文件,并通过 sourceSet 来引入参与编译

// 获取插件内配置的权限,并将权限添加进 manifest 节点
getPermission().forEach {
    childrenNode.add(0, Node(null, it))
}
// 将权限写入临时清单文件
val xmlText2 = XmlUtil.serialize(parentNode)
permissionFile.writeText(xmlText2)

project.afterEvaluate {
    // 将生成的临时清单文件添加进 main sourceSet.manifest 中参与项目编译
    project.extensions.getByType(AppExtension::class.java)
        .sourceSets.find { it.name == "main" }?.manifest?.srcFile(permissionFile)
}

源码可查看 PermissionPlugin

扩展知识(清单文件合并)

合并优先级: image.png 合并三个清单文件的流程,从优先级最低的清单文件(左)合并到优先级最高的清单文件(右)中

因此,在组件模块中声明的权限(Library)比主工程声明的权限(main)优先级低,所以可以在优先级高的主工程模块声明 remove 规则,即可将低优先级中声明的权限进行移除,例如:

image.png 合并结果,只保留了 INTERNET 权限:

image.png 所以,我们又可以发散性思维,对于 Android 12 的 exported 适配,是不是也可以利用这种规则,对于原本需要在组件中声明的 exported,我们可以利用脚本来处理,生成和组件中一样的节点,并声明 exported 为 false,并且增加一个 merge 节点来参与合并,以此给组件原来未声明的 exported 添加上,示例如下: image.png 合并结果如下:

image.png