blob: 713ad84f9db3a4d5a62a9b6b36a75bcc2d40508c [file] [log] [blame]
/*
* 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.android.tools.lint.checks
import com.android.testutils.TestUtils
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.CUSTOM_PERMISSION_TYPO
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.KNOWN_PERMISSION_ERROR
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.PERMISSION_NAMING_CONVENTION
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.RESERVED_SYSTEM_PERMISSION
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.SYSTEM_PERMISSION_TYPO
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.findAlmostCustomPermission
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.findAlmostPlatformPermission
import com.android.tools.lint.checks.PermissionErrorDetector.Companion.permissionToPrefixAndSuffix
import com.android.tools.lint.checks.SystemPermissionsDetector.SYSTEM_PERMISSIONS
import com.android.tools.lint.checks.infrastructure.ProjectDescription
import com.android.tools.lint.detector.api.Detector
import com.google.common.io.Files
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.junit.Test
import org.junit.rules.TemporaryFolder
class PermissionErrorDetectorTest : AbstractCheckTest() {
override fun getDetector(): Detector = PermissionErrorDetector()
@Test
fun testDocumentationExamplePermissionNamingConvention() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<permission android:name="com.example.helloworld.permission.FOO_BAR" />
<permission android:name="com.example.helloworld.specific.path.permission.FOO_BAR" />
<permission android:name="com.example.helloworld.permission.FOO_BAR_123" />
<permission android:name="com.example.helloworld.FOO_BAR" />
<permission android:name="com.example.helloworld.permission.FOO-BAR" />
<permission android:name="com.example.helloworld.permission.foo_bar" />
<permission android:name="android.permission.FOO_BAR" />
<permission android:name="FOO_BAR" />
</manifest>
"""
).indented()
)
.issues(PERMISSION_NAMING_CONVENTION)
.run()
.expect(
"""
AndroidManifest.xml:7: Warning: com.example.helloworld.FOO_BAR does not follow recommended naming convention [PermissionNamingConvention]
<permission android:name="com.example.helloworld.FOO_BAR" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:8: Warning: com.example.helloworld.permission.FOO-BAR does not follow recommended naming convention [PermissionNamingConvention]
<permission android:name="com.example.helloworld.permission.FOO-BAR" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:9: Warning: com.example.helloworld.permission.foo_bar does not follow recommended naming convention [PermissionNamingConvention]
<permission android:name="com.example.helloworld.permission.foo_bar" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:10: Warning: android.permission.FOO_BAR does not follow recommended naming convention [PermissionNamingConvention]
<permission android:name="android.permission.FOO_BAR" />
~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:11: Warning: FOO_BAR does not follow recommended naming convention [PermissionNamingConvention]
<permission android:name="FOO_BAR" />
~~~~~~~
0 errors, 5 warnings
"""
)
.expectFixDiffs("")
}
@Test
fun testDocumentationExampleKnownPermissionError() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<application android:permission="true">
<activity android:permission="TRUE" />
<activity-alias android:permission="True" />
<receiver android:permission="true" />
<service android:permission="false" />
<provider android:permission="false" />
</application>
</manifest>
"""
).indented()
)
.issues(KNOWN_PERMISSION_ERROR)
.run()
.expect(
"""
AndroidManifest.xml:4: Error: true is not a valid permission value [KnownPermissionError]
<application android:permission="true">
~~~~
AndroidManifest.xml:5: Error: TRUE is not a valid permission value [KnownPermissionError]
<activity android:permission="TRUE" />
~~~~
AndroidManifest.xml:6: Error: True is not a valid permission value [KnownPermissionError]
<activity-alias android:permission="True" />
~~~~
AndroidManifest.xml:7: Error: true is not a valid permission value [KnownPermissionError]
<receiver android:permission="true" />
~~~~
AndroidManifest.xml:8: Error: false is not a valid permission value [KnownPermissionError]
<service android:permission="false" />
~~~~~
AndroidManifest.xml:9: Error: false is not a valid permission value [KnownPermissionError]
<provider android:permission="false" />
~~~~~
6 errors, 0 warnings
"""
)
.expectFixDiffs("")
}
@Test
fun testKnownPermissionErrorOk() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<application>
<activity android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" />
</application>
</manifest>
"""
).indented()
)
.issues(KNOWN_PERMISSION_ERROR)
.run()
.expectClean()
}
@Test
fun testDocumentationExampleReservedSystemPermission() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<permission android:name="android.permission.BIND_APPWIDGET" />
<application>
<service android:permission="android.permission.BIND_APPWIDGET" />
</application>
</manifest>
"""
).indented()
)
.issues(RESERVED_SYSTEM_PERMISSION)
.run()
.expect(
"""
AndroidManifest.xml:4: Error: android.permission.BIND_APPWIDGET is a reserved permission [ReservedSystemPermission]
<permission android:name="android.permission.BIND_APPWIDGET" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
@Test
fun testReservedSystemPermissionOk() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<permission android:name="com.example.BIND_APPWIDGET" />
<application>
<service android:permission="android.permission.BIND_APPWIDGET" />
</application>
</manifest>
"""
).indented()
)
.issues(RESERVED_SYSTEM_PERMISSION)
.run()
.expectClean()
}
@Test
fun testDocumentationExampleSystemPermissionTypo() {
lint().files(
manifest(
"""
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<uses-permission android:name="android.permission.BIND_NCF_SERVICE" />
<application android:name="App" android:permission="android.permission.BIND_NCF_SERVICE">
<activity />
<activity android:permission="android.permission.BIND_NFC_SERVICE" />
<activity android:permission="android.permission.BIND_NCF_SERVICE" />
<activity-alias android:permission="android.permission.BIND_NCF_SERVICE" />
<receiver android:permission="android.permission.BIND_NCF_SERVICE" />
<service android:permission="android.permission.BIND_NCF_SERVICE" />
<provider android:permission="android.permission.BIND_NCF_SERVICE" />
</application>
</manifest>
"""
).indented()
)
.issues(SYSTEM_PERMISSION_TYPO)
.run()
.expect(
"""
AndroidManifest.xml:5: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<uses-permission android:name="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:6: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<application android:name="App" android:permission="android.permission.BIND_NCF_SERVICE">
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:9: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<activity android:permission="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:10: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<activity-alias android:permission="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:11: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<receiver android:permission="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:12: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<service android:permission="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:13: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<provider android:permission="android.permission.BIND_NCF_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 7 warnings
"""
)
.expectFixDiffs(
"""
Fix for AndroidManifest.xml line 5: Replace with android.permission.BIND_NFC_SERVICE:
@@ -5 +5
- <uses-permission android:name="android.permission.BIND_NCF_SERVICE" />
+ <uses-permission android:name="android.permission.BIND_NFC_SERVICE" />
Fix for AndroidManifest.xml line 6: Replace with android.permission.BIND_NFC_SERVICE:
@@ -6 +6
- <application android:name="App" android:permission="android.permission.BIND_NCF_SERVICE">
+ <application android:name="App" android:permission="android.permission.BIND_NFC_SERVICE">
Fix for AndroidManifest.xml line 9: Replace with android.permission.BIND_NFC_SERVICE:
@@ -9 +9
- <activity android:permission="android.permission.BIND_NCF_SERVICE" />
+ <activity android:permission="android.permission.BIND_NFC_SERVICE" />
Fix for AndroidManifest.xml line 10: Replace with android.permission.BIND_NFC_SERVICE:
@@ -10 +10
- <activity-alias android:permission="android.permission.BIND_NCF_SERVICE" />
+ <activity-alias android:permission="android.permission.BIND_NFC_SERVICE" />
Fix for AndroidManifest.xml line 11: Replace with android.permission.BIND_NFC_SERVICE:
@@ -11 +11
- <receiver android:permission="android.permission.BIND_NCF_SERVICE" />
+ <receiver android:permission="android.permission.BIND_NFC_SERVICE" />
Fix for AndroidManifest.xml line 12: Replace with android.permission.BIND_NFC_SERVICE:
@@ -12 +12
- <service android:permission="android.permission.BIND_NCF_SERVICE" />
+ <service android:permission="android.permission.BIND_NFC_SERVICE" />
Fix for AndroidManifest.xml line 13: Replace with android.permission.BIND_NFC_SERVICE:
@@ -13 +13
- <provider android:permission="android.permission.BIND_NCF_SERVICE" />
+ <provider android:permission="android.permission.BIND_NFC_SERVICE" />
"""
)
}
@Test
fun testSystemPermissionTypoPrefix() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<application>
<activity />
<service android:permission="android.Manifest.permission.BIND_NFC_SERVICE" />
<service android:permission="android.permission.WAKE_LOCK" />
</application>
</manifest>
"""
).indented()
)
.issues(SYSTEM_PERMISSION_TYPO)
.run()
.expect(
"""
AndroidManifest.xml:6: Warning: Did you mean android.permission.BIND_NFC_SERVICE? [SystemPermissionTypo]
<service android:permission="android.Manifest.permission.BIND_NFC_SERVICE" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
.expectFixDiffs(
"""
Fix for AndroidManifest.xml line 5: Replace with android.permission.BIND_NFC_SERVICE:
@@ -6 +6
- <service android:permission="android.Manifest.permission.BIND_NFC_SERVICE" />
+ <service android:permission="android.permission.BIND_NFC_SERVICE" />
"""
)
}
@Test
fun testSystemPermissionTypoOk() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<application>
<activity />
<service android:permission="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
</application>
</manifest>
"""
).indented()
)
.issues(SYSTEM_PERMISSION_TYPO)
.run()
.expectClean()
}
@Test
fun testFindAlmostSystemPermission() {
// Set up a simple compilation environment such that we can grab a project out of it to use for unit testing
// the findAlmostSystemPermission (which needs access to the java evaluator)
val temp = Files.createTempDir()
val temporaryFolder = TemporaryFolder(temp)
temporaryFolder.create()
val parsed = com.android.tools.lint.checks.infrastructure.parseFirst(
sdkHome = TestUtils.getSdk().toFile(),
temporaryFolder = temporaryFolder,
testFiles = arrayOf(
java(
"class Test { }"
)
)
)
val disposable = Disposable {
Disposer.dispose(parsed.second)
temp.deleteRecursively()
}
val context = parsed.first
val project = context.project
// well-known cases are handled
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.permission.BIND_NCF_SERVICE")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.Manifest.permission.BIND_NCF_SERVICE")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.permission.bind_ncf_service")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.permission\n .BIND_NCF_@@--~~SERVICE")
)
assertEquals(
"android.permission.BLUETOOTH_PRIVILEGED",
findAlmostPlatformPermission(
project,
"""
android.permission.BIND_NFC_SERVICE |
android.permission.SYSTEM_ALERT_WINDOW |
android.permission.BLUETOOTH_PRIVILEGED
""".trimMargin()
)
)
// Matching based on just the name part
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "@ndr\$oid@.BIND_NCF_SERVICE")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "\${MY_SUBSTITUTION}.BIND_NCF_SERVICE")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.BIND_NCF_SERVICE")
)
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "adroid.prmission.BIND_NCF_SERVICE") // typos in package name
)
// assure we don't match one valid permission against another
for (systemPermission in SYSTEM_PERMISSIONS) {
assertNull(findAlmostPlatformPermission(project, systemPermission))
}
// assure we don't match against a clearly custom package
assertNull(
findAlmostPlatformPermission(project, "my.custom.package.CAMERA")
)
assertNull(
findAlmostPlatformPermission(project, "my.custom.package.CMERA")
)
// assure the edit distance logic behaves as expected per the MAX_EDIT_DISTANCE const
assertEquals(
"android.permission.BIND_NFC_SERVICE",
findAlmostPlatformPermission(project, "android.permission.BIND_NFC_SERVZZZ")
)
assertNull(
findAlmostPlatformPermission(project, "android.permission.BIND_NFC_SERZZZZ")
)
Disposer.dispose(disposable)
}
@Test
fun testPermissionToPrefixAndSuffix() {
val (prefix1, suffix1) = permissionToPrefixAndSuffix("foo")
assertEquals(prefix1, "")
assertEquals(suffix1, "foo")
val (prefix2, suffix2) = permissionToPrefixAndSuffix("android.permission.BIND_NFC_SERVICE")
assertEquals(prefix2, "android.permission")
assertEquals(suffix2, "BIND_NFC_SERVICE")
}
@Test
fun testDocumentationExampleCustomPermissionTypo() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<permission android:name="my.custom.permission.FOOBAR" />
<permission android:name="my.custom.permission.FOOBAB" />
<permission android:name="my.custom.permission.BAZQUXX" />
<permission android:name="my.custom.permission.BAZQUZZ" />
<application>
<service android:permission="my.custom.permission.FOOBOB" />
<service android:permission="my.custom.permission.FOOBAB" />
<activity android:permission="my.custom.permission.BAZQXX" />
<activity android:permission="my.custom.permission.BAZQUZZ" />
<activity android:permission="my.custom.permission.WAKE_LOCK" />
</application>
</manifest>
"""
).indented()
)
.issues(CUSTOM_PERMISSION_TYPO)
.run()
.expect(
"""
AndroidManifest.xml:9: Warning: Did you mean my.custom.permission.FOOBAR? [CustomPermissionTypo]
<service android:permission="my.custom.permission.FOOBOB" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:11: Warning: Did you mean my.custom.permission.BAZQUXX? [CustomPermissionTypo]
<activity android:permission="my.custom.permission.BAZQXX" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
"""
)
.expectFixDiffs(
"""
Fix for AndroidManifest.xml line 8: Replace with my.custom.permission.FOOBAR:
@@ -9 +9
- <service android:permission="my.custom.permission.FOOBOB" />
+ <service android:permission="my.custom.permission.FOOBAR" />
Fix for AndroidManifest.xml line 10: Replace with my.custom.permission.BAZQUXX:
@@ -11 +11
- <activity android:permission="my.custom.permission.BAZQXX" />
+ <activity android:permission="my.custom.permission.BAZQUXX" />
"""
)
}
@Test
fun testCustomPermissionTypoOk() {
lint().files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.helloworld">
<permission android:name="my.custom.permission.FOOBAR" />
<permission android:name="my.custom.permission.BAZQUXX" />
<application>
<service android:permission="my.custom.permission.FOOBAR" />
<activity android:permission="my.custom.permission.BAZQUXX" />
</application>
</manifest>
"""
).indented()
)
.issues(CUSTOM_PERMISSION_TYPO)
.run().expectClean()
}
@Test
fun testCustomPermissionTypoWithMergedManifest() {
val library = project(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld.lib"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" />
<permission android:name="my.custom.permission.FOOBAR"
android:label="@string/foo"
android:description="@string/foo" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
</application>
</manifest>
"""
).indented()
).type(ProjectDescription.Type.LIBRARY).name("Library")
val main = project(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld.app"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" />
<uses-permission android:name="my.custom.permission.FOOBOB" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
</application>
</manifest>
"""
).indented()
).name("App").dependsOn(library)
lint().projects(main, library)
.issues(CUSTOM_PERMISSION_TYPO)
.run()
.expect(
"""
AndroidManifest.xml:6: Warning: Did you mean my.custom.permission.FOOBAR? [CustomPermissionTypo]
<uses-permission android:name="my.custom.permission.FOOBOB" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
.expectFixDiffs(
"""
Fix for AndroidManifest.xml line 6: Replace with my.custom.permission.FOOBAR:
@@ -6 +6
- <uses-permission android:name="my.custom.permission.FOOBOB" />
+ <uses-permission android:name="my.custom.permission.FOOBAR" />
"""
)
}
@Test
fun testFindAlmostCustomPermission() {
val customPermissions = listOf("my.custom.permission.FOO_BAR", "my.custom.permission.BAZ_QUXX")
assertEquals(
findAlmostCustomPermission("my.custom.permission.FOOB", customPermissions),
"my.custom.permission.FOO_BAR"
)
assertEquals(
findAlmostCustomPermission("my.custom.permission.BAZQUXX", customPermissions),
"my.custom.permission.BAZ_QUXX"
)
assertEquals(
findAlmostCustomPermission("my.custom.permission.BAZ_QZZZ", customPermissions),
"my.custom.permission.BAZ_QUXX"
)
assertNull(
findAlmostCustomPermission("my.custom.permission.BAZ_ZZZZ", customPermissions),
)
}
@Test
fun testFindAlmostCustomPermission_noFalsePositives() {
val customPermissions = listOf("my.custom.permission.FOO_BAR", "my.custom.permission.FOO_BAZ")
customPermissions.forEach {
assertNull(findAlmostCustomPermission(it, customPermissions))
}
}
}