Refactor ManualPermissionCheckDetector for @PermissionMethod
Uses @PermissionMethod/@PermissionName annotations to find and auto-fix "manual" permission checks that can be migrated to @EnforcePermission (as opposed to a hard-coded list of known permission methods). Bug: 247537842 Test: ManualPermissionCheckDetectorTest Change-Id: Ibd842f0e64e2006954bffa24c7fe394903dd7b61
This commit is contained in:
@@ -26,6 +26,7 @@ import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.android.tools.lint.detector.api.SourceCodeScanner
|
||||
import com.android.tools.lint.detector.api.getUMethod
|
||||
import com.google.android.lint.aidl.hasPermissionMethodAnnotation
|
||||
import com.intellij.psi.PsiType
|
||||
import org.jetbrains.uast.UAnnotation
|
||||
import org.jetbrains.uast.UBlockExpression
|
||||
@@ -149,11 +150,6 @@ class PermissionMethodDetector : Detector(), SourceCodeScanner {
|
||||
enabledByDefault = false
|
||||
)
|
||||
|
||||
private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
|
||||
.any {
|
||||
it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
|
||||
}
|
||||
|
||||
private fun isPermissionMethodReturnType(method: UMethod): Boolean =
|
||||
listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
|
||||
|
||||
|
||||
@@ -18,37 +18,54 @@ package com.google.android.lint.aidl
|
||||
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.Location
|
||||
import com.intellij.psi.PsiVariable
|
||||
import com.android.tools.lint.detector.api.getUMethod
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.ULiteralExpression
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression
|
||||
import org.jetbrains.uast.USimpleNameReferenceExpression
|
||||
import org.jetbrains.uast.asRecursiveLogString
|
||||
import org.jetbrains.uast.evaluateString
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor
|
||||
|
||||
/**
|
||||
* Helper ADT class that facilitates the creation of lint auto fixes
|
||||
* Helper class that facilitates the creation of lint auto fixes
|
||||
*
|
||||
* Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
|
||||
* that should be migrated to @EnforcePermission(allOf={...})
|
||||
*
|
||||
* TODO: handle anyOf style annotations
|
||||
*/
|
||||
sealed class EnforcePermissionFix {
|
||||
abstract fun locations(): List<Location>
|
||||
abstract fun javaAnnotationParameter(): String
|
||||
|
||||
fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
|
||||
data class EnforcePermissionFix(
|
||||
val locations: List<Location>,
|
||||
val permissionNames: List<String>
|
||||
) {
|
||||
val annotation: String
|
||||
get() {
|
||||
val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
|
||||
val annotationParameter =
|
||||
if (permissionNames.size > 1) "allOf={$quotedPermissions}" else quotedPermissions
|
||||
return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
|
||||
SingleFix(
|
||||
getPermissionCheckLocation(context, callExpression),
|
||||
getPermissionCheckArgumentValue(callExpression)
|
||||
)
|
||||
/**
|
||||
* conditionally constructs EnforcePermissionFix from a UCallExpression
|
||||
* @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
|
||||
*/
|
||||
fun fromCallExpression(
|
||||
context: JavaContext,
|
||||
callExpression: UCallExpression
|
||||
): EnforcePermissionFix? =
|
||||
if (isPermissionMethodCall(callExpression)) {
|
||||
EnforcePermissionFix(
|
||||
listOf(getPermissionCheckLocation(context, callExpression)),
|
||||
getPermissionCheckValues(callExpression)
|
||||
)
|
||||
} else null
|
||||
|
||||
fun maybeAddManifestPrefix(permissionName: String): String =
|
||||
if (permissionName.contains(".")) permissionName
|
||||
else "android.Manifest.permission.$permissionName"
|
||||
|
||||
fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
|
||||
EnforcePermissionFix(
|
||||
individuals.flatMap { it.locations },
|
||||
individuals.flatMap { it.permissionNames }
|
||||
)
|
||||
|
||||
/**
|
||||
* Given a permission check, get its proper location
|
||||
@@ -70,49 +87,51 @@ sealed class EnforcePermissionFix {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a permission check and an argument,
|
||||
* pull out the permission value that is being used
|
||||
* Given a @PermissionMethod, find arguments annotated with @PermissionName
|
||||
* and pull out the permission value(s) being used. Also evaluates nested calls
|
||||
* to @PermissionMethod(s) in the given method's body.
|
||||
*/
|
||||
private fun getPermissionCheckArgumentValue(
|
||||
callExpression: UCallExpression,
|
||||
argumentPosition: Int = 0
|
||||
): String {
|
||||
private fun getPermissionCheckValues(
|
||||
callExpression: UCallExpression
|
||||
): List<String> {
|
||||
if (!isPermissionMethodCall(callExpression)) return emptyList()
|
||||
|
||||
val identifier = when (
|
||||
val argument = callExpression.valueArguments.getOrNull(argumentPosition)
|
||||
) {
|
||||
is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
|
||||
is USimpleNameReferenceExpression ->
|
||||
((selector.resolve() as PsiVariable).computeConstantValue() as String)
|
||||
val result = mutableSetOf<String>() // protect against duplicate permission values
|
||||
val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
|
||||
val bfsQueue = ArrayDeque(listOf(callExpression))
|
||||
|
||||
else -> throw RuntimeException(
|
||||
"Couldn't resolve argument: ${selector.asRecursiveLogString()}"
|
||||
)
|
||||
}
|
||||
// Breadth First Search - evalutaing nested @PermissionMethod(s) in the available
|
||||
// source code for @PermissionName(s).
|
||||
while (bfsQueue.isNotEmpty()) {
|
||||
val current = bfsQueue.removeFirst()
|
||||
visitedCalls.add(current)
|
||||
result.addAll(findPermissions(current))
|
||||
|
||||
is USimpleNameReferenceExpression -> (
|
||||
(argument.resolve() as PsiVariable).computeConstantValue() as String)
|
||||
|
||||
is ULiteralExpression -> argument.value as String
|
||||
|
||||
else -> throw RuntimeException(
|
||||
"Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
|
||||
)
|
||||
current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() {
|
||||
override fun visitCallExpression(node: UCallExpression): Boolean {
|
||||
if (isPermissionMethodCall(node) && node !in visitedCalls) {
|
||||
bfsQueue.add(node)
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return identifier.substringAfterLast(".")
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
private fun findPermissions(
|
||||
callExpression: UCallExpression,
|
||||
): List<String> {
|
||||
val indices = callExpression.resolve()?.getUMethod()
|
||||
?.uastParameters
|
||||
?.filter(::hasPermissionNameAnnotation)
|
||||
?.mapNotNull { it.sourcePsi?.parameterIndex() }
|
||||
?: emptyList()
|
||||
|
||||
return indices.mapNotNull {
|
||||
callExpression.getArgumentForParameter(it)?.evaluateString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
|
||||
override fun locations(): List<Location> = listOf(this.location)
|
||||
override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
|
||||
}
|
||||
data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
|
||||
override fun locations(): List<Location> = this.checks.map { it.location }
|
||||
override fun javaAnnotationParameter(): String =
|
||||
"allOf={${
|
||||
this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
|
||||
}}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.lint.aidl
|
||||
|
||||
import com.android.tools.lint.detector.api.getUMethod
|
||||
import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
|
||||
import com.google.android.lint.ANNOTATION_PERMISSION_NAME
|
||||
import com.google.android.lint.CLASS_STUB
|
||||
import com.intellij.psi.PsiAnonymousClass
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UMethod
|
||||
import org.jetbrains.uast.UParameter
|
||||
|
||||
/**
|
||||
* Given a UMethod, determine if this method is
|
||||
* an entrypoint to an interface generated by AIDL,
|
||||
* returning the interface name if so
|
||||
*/
|
||||
fun getContainingAidlInterface(node: UMethod): String? {
|
||||
if (!isInClassCalledStub(node)) return null
|
||||
for (superMethod in node.findSuperMethods()) {
|
||||
for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
|
||||
?: continue) {
|
||||
if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
|
||||
return superMethod.containingClass?.name
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isInClassCalledStub(node: UMethod): Boolean {
|
||||
(node.containingClass as? PsiAnonymousClass)?.let {
|
||||
return it.baseClassReference.referenceName == CLASS_STUB
|
||||
}
|
||||
return node.containingClass?.extendsList?.referenceElements?.any {
|
||||
it.referenceName == CLASS_STUB
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
|
||||
val method = callExpression.resolve()?.getUMethod() ?: return false
|
||||
return hasPermissionMethodAnnotation(method)
|
||||
}
|
||||
|
||||
fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
|
||||
.any {
|
||||
it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
|
||||
}
|
||||
|
||||
fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
|
||||
it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
|
||||
}
|
||||
@@ -25,9 +25,6 @@ import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.android.tools.lint.detector.api.SourceCodeScanner
|
||||
import com.google.android.lint.CLASS_STUB
|
||||
import com.google.android.lint.ENFORCE_PERMISSION_METHODS
|
||||
import com.intellij.psi.PsiAnonymousClass
|
||||
import org.jetbrains.uast.UBlockExpression
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UElement
|
||||
@@ -56,7 +53,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
val body = (node.uastBody as? UBlockExpression) ?: return
|
||||
val fix = accumulateSimplePermissionCheckFixes(body) ?: return
|
||||
|
||||
val javaRemoveFixes = fix.locations().map {
|
||||
val javaRemoveFixes = fix.locations.map {
|
||||
fix()
|
||||
.replace()
|
||||
.reformat(true)
|
||||
@@ -67,7 +64,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
}
|
||||
|
||||
val javaAnnotateFix = fix()
|
||||
.annotate(fix.javaAnnotation())
|
||||
.annotate(fix.annotation)
|
||||
.range(context.getLocation(node))
|
||||
.autoFix()
|
||||
.build()
|
||||
@@ -77,7 +74,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
|
||||
context.report(
|
||||
ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
|
||||
fix.locations().last(),
|
||||
fix.locations.last(),
|
||||
message,
|
||||
fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
|
||||
)
|
||||
@@ -97,14 +94,14 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
*/
|
||||
private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
|
||||
EnforcePermissionFix? {
|
||||
val singleFixes = mutableListOf<SingleFix>()
|
||||
val singleFixes = mutableListOf<EnforcePermissionFix>()
|
||||
for (expression in methodBody.expressions) {
|
||||
singleFixes.add(getPermissionCheckFix(expression) ?: break)
|
||||
}
|
||||
return when (singleFixes.size) {
|
||||
0 -> null
|
||||
1 -> singleFixes[0]
|
||||
else -> AllOfFix(singleFixes)
|
||||
else -> EnforcePermissionFix.compose(singleFixes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +110,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
* the helper for creating a lint auto fix to @EnforcePermission
|
||||
*/
|
||||
private fun getPermissionCheckFix(startingExpression: UElement?):
|
||||
SingleFix? {
|
||||
EnforcePermissionFix? {
|
||||
return when (startingExpression) {
|
||||
is UQualifiedReferenceExpression -> getPermissionCheckFix(
|
||||
startingExpression.selector
|
||||
@@ -121,11 +118,8 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
|
||||
is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
|
||||
|
||||
is UCallExpression -> {
|
||||
return if (isPermissionCheck(startingExpression))
|
||||
EnforcePermissionFix.fromCallExpression(startingExpression, context)
|
||||
else null
|
||||
}
|
||||
is UCallExpression -> return EnforcePermissionFix
|
||||
.fromCallExpression(context, startingExpression)
|
||||
|
||||
else -> null
|
||||
}
|
||||
@@ -160,40 +154,5 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
|
||||
),
|
||||
enabledByDefault = false, // TODO: enable once b/241171714 is resolved
|
||||
)
|
||||
|
||||
private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
|
||||
val method = callExpression.resolve() ?: return false
|
||||
val className = method.containingClass?.qualifiedName
|
||||
return ENFORCE_PERMISSION_METHODS.any {
|
||||
it.clazz == className && it.name == method.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* given a UMethod, determine if this method is
|
||||
* an entrypoint to an interface generated by AIDL,
|
||||
* returning the interface name if so
|
||||
*/
|
||||
fun getContainingAidlInterface(node: UMethod): String? {
|
||||
if (!isInClassCalledStub(node)) return null
|
||||
for (superMethod in node.findSuperMethods()) {
|
||||
for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
|
||||
?: continue) {
|
||||
if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
|
||||
return superMethod.containingClass?.name
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isInClassCalledStub(node: UMethod): Boolean {
|
||||
(node.containingClass as? PsiAnonymousClass)?.let {
|
||||
return it.baseClassReference.referenceName == CLASS_STUB
|
||||
}
|
||||
return node.containingClass?.extendsList?.referenceElements?.any {
|
||||
it.referenceName == CLASS_STUB
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.google.android.lint.aidl
|
||||
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
|
||||
import com.android.tools.lint.checks.infrastructure.TestFile
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import com.android.tools.lint.checks.infrastructure.TestMode
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
|
||||
@@ -42,7 +43,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
private Context mContext;
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -53,8 +54,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
.expect(
|
||||
"""
|
||||
src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
|
||||
mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
@@ -62,9 +63,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
"""
|
||||
Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
|
||||
@@ -5 +5
|
||||
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
|
||||
+ @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
|
||||
@@ -7 +8
|
||||
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
- mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
"""
|
||||
)
|
||||
}
|
||||
@@ -81,7 +82,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
"android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
"android.permission.READ_CONTACTS", "foo");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -102,10 +103,49 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
"""
|
||||
Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
|
||||
@@ -6 +6
|
||||
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
|
||||
+ @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
|
||||
@@ -8 +9
|
||||
- mContext.enforceCallingOrSelfPermission(
|
||||
- "android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
- "android.permission.READ_CONTACTS", "foo");
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
fun testConstantEvaluation() {
|
||||
lint().files(
|
||||
java(
|
||||
"""
|
||||
import android.content.Context;
|
||||
import android.test.ITest;
|
||||
|
||||
public class Foo extends ITest.Stub {
|
||||
private Context mContext;
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
|
||||
}
|
||||
}
|
||||
"""
|
||||
).indented(),
|
||||
*stubs,
|
||||
manifestStub
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
|
||||
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
|
||||
@@ -6 +6
|
||||
+ @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
|
||||
@@ -8 +9
|
||||
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
|
||||
"""
|
||||
)
|
||||
}
|
||||
@@ -122,9 +162,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
"android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
"android.permission.READ_CONTACTS", "foo");
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
"android.Manifest.permission.WRITE_CONTACTS", "foo");
|
||||
"android.permission.WRITE_CONTACTS", "foo");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -144,13 +184,13 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
|
||||
@@ -6 +6
|
||||
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
|
||||
@@ -6 +6
|
||||
+ @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
|
||||
@@ -8 +9
|
||||
- mContext.enforceCallingOrSelfPermission(
|
||||
- "android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
- "android.permission.READ_CONTACTS", "foo");
|
||||
- mContext.enforceCallingOrSelfPermission(
|
||||
- "android.Manifest.permission.WRITE_CONTACTS", "foo");
|
||||
- "android.permission.WRITE_CONTACTS", "foo");
|
||||
"""
|
||||
)
|
||||
}
|
||||
@@ -166,7 +206,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
long uid = Binder.getCallingUid();
|
||||
mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -177,6 +217,149 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
fun testPermissionHelper() {
|
||||
lint().skipTestModes(TestMode.PARENTHESIZED).files(
|
||||
java(
|
||||
"""
|
||||
import android.content.Context;
|
||||
import android.test.ITest;
|
||||
|
||||
public class Foo extends ITest.Stub {
|
||||
private Context mContext;
|
||||
|
||||
@android.content.pm.PermissionMethod
|
||||
private void helper() {
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
helper();
|
||||
}
|
||||
}
|
||||
"""
|
||||
).indented(),
|
||||
*stubs
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
|
||||
helper();
|
||||
~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
|
||||
@@ -12 +12
|
||||
+ @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
|
||||
@@ -14 +15
|
||||
- helper();
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
fun testPermissionHelperAllOf() {
|
||||
lint().skipTestModes(TestMode.PARENTHESIZED).files(
|
||||
java(
|
||||
"""
|
||||
import android.content.Context;
|
||||
import android.test.ITest;
|
||||
|
||||
public class Foo extends ITest.Stub {
|
||||
private Context mContext;
|
||||
|
||||
@android.content.pm.PermissionMethod
|
||||
private void helper() {
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
helper();
|
||||
mContext.enforceCallingOrSelfPermission("FOO", "foo");
|
||||
}
|
||||
}
|
||||
"""
|
||||
).indented(),
|
||||
*stubs
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
|
||||
mContext.enforceCallingOrSelfPermission("FOO", "foo");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
|
||||
@@ -13 +13
|
||||
+ @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
|
||||
@@ -15 +16
|
||||
- helper();
|
||||
- mContext.enforceCallingOrSelfPermission("FOO", "foo");
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun testPermissionHelperNested() {
|
||||
lint().skipTestModes(TestMode.PARENTHESIZED).files(
|
||||
java(
|
||||
"""
|
||||
import android.content.Context;
|
||||
import android.test.ITest;
|
||||
|
||||
public class Foo extends ITest.Stub {
|
||||
private Context mContext;
|
||||
|
||||
@android.content.pm.PermissionMethod
|
||||
private void helperHelper() {
|
||||
helper("android.permission.WRITE_CONTACTS");
|
||||
}
|
||||
|
||||
@android.content.pm.PermissionMethod
|
||||
private void helper(@android.content.pm.PermissionName String extraPermission) {
|
||||
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() throws android.os.RemoteException {
|
||||
helperHelper();
|
||||
}
|
||||
}
|
||||
"""
|
||||
).indented(),
|
||||
*stubs
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
|
||||
helperHelper();
|
||||
~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
|
||||
@@ -17 +17
|
||||
+ @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
|
||||
@@ -19 +20
|
||||
- helperHelper();
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
private val aidlStub: TestFile = java(
|
||||
"""
|
||||
@@ -192,7 +375,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
"""
|
||||
package android.content;
|
||||
public class Context {
|
||||
public void enforceCallingOrSelfPermission(String permission, String message) {}
|
||||
@android.content.pm.PermissionMethod
|
||||
public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
|
||||
}
|
||||
"""
|
||||
).indented()
|
||||
@@ -206,6 +390,59 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
|
||||
"""
|
||||
).indented()
|
||||
|
||||
val stubs = arrayOf(aidlStub, contextStub, binderStub)
|
||||
private val permissionMethodStub: TestFile = java(
|
||||
"""
|
||||
package android.content.pm;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.CLASS;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(CLASS)
|
||||
@Target({METHOD})
|
||||
public @interface PermissionMethod {}
|
||||
"""
|
||||
).indented()
|
||||
|
||||
private val permissionNameStub: TestFile = java(
|
||||
"""
|
||||
package android.content.pm;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.CLASS;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(CLASS)
|
||||
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
|
||||
public @interface PermissionName {}
|
||||
"""
|
||||
).indented()
|
||||
|
||||
private val manifestStub: TestFile = java(
|
||||
"""
|
||||
package android;
|
||||
|
||||
public final class Manifest {
|
||||
public static final class permission {
|
||||
public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val stubs = arrayOf(
|
||||
aidlStub,
|
||||
contextStub,
|
||||
binderStub,
|
||||
permissionMethodStub,
|
||||
permissionNameStub
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user