Loading services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +41 −11 Original line number Diff line number Diff line Loading @@ -986,9 +986,12 @@ class AppIdPermissionPolicy : SchemePolicy() { return } if (permission.isNormal) { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) var newFlags: Int val wasInstallGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) val wasInstallRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) if (wasInstallGranted || !wasInstallRevoked) { newFlags = PermissionFlags.INSTALL_GRANTED } else { val isRequestedByInstalledPackage = installedPackageState != null && permissionName in Loading @@ -1002,9 +1005,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform var newFlags = newFlags = if ( !wasRevoked || isRequestedByInstalledPackage || isRequestedBySystemPackage || isCompatibilityPermission Loading @@ -1013,13 +1015,25 @@ class AppIdPermissionPolicy : SchemePolicy() { } else { PermissionFlags.INSTALL_REVOKED } } // Starting from Android 17, an app requesting permission which requires purpose must // declare at least one valid purpose in its manifest before it can be granted. Note // that a flag state may have INSTALL_GRANTED and PURPOSE_REVOKED bits set, in which // case the permission will not be granted. if (Flags.purposeDeclarationEnabled() && permission.requiresPurpose) { val hasValidPurpose = requestingPackageStates.anyIndexed { _, it -> hasValidPurposeForPackage(it.androidPackage!!, permission) } if (!hasValidPurpose) { newFlags = newFlags or PermissionFlags.PURPOSE_REVOKED } } if (permission.isAppOp) { newFlags = newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) var newFlags = Loading Loading @@ -1307,6 +1321,22 @@ class AppIdPermissionPolicy : SchemePolicy() { return false } // TODO(b/422817717) - Handle app-compat scenarios by using versioning info at the purpose level private fun hasValidPurposeForPackage( androidPackage: AndroidPackage, permission: Permission, ): Boolean { // TODO(b/419394842) - Use < Android C build version code when available. if (androidPackage.targetSdkVersion <= Build.VERSION_CODES.BAKLAVA) { // Purpose declaration is not supported on older target versions. Bypass check. return true } val purposes = androidPackage.usesPermissionMapping[permission.name]?.purposes // There must be at least one valid purpose defined as the parsing logic would otherwise // force set the permission to not require purpose, making this code unreachable. return purposes?.any { it in permission.validPurposes } ?: false } private fun MutateStateScope.shouldGrantPermissionBySignature( packageState: PackageState, permission: Permission, Loading services/permission/java/com/android/server/permission/access/permission/Permission.kt +6 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,12 @@ data class Permission( inline val knownCerts: Set<String> get() = permissionInfo.knownCerts inline val requiresPurpose: Boolean get() = permissionInfo.requiresPurpose inline val validPurposes: Set<String> get() = permissionInfo.validPurposes inline val hasGids: Boolean get() = gids.isNotEmpty() Loading services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +11 −0 Original line number Diff line number Diff line Loading @@ -294,6 +294,12 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 /** * Permission flag for a normal permission that is revoked at install-time due to failing * purpose validation check. */ const val PURPOSE_REVOKED = 1 shl 24 /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() Loading Loading @@ -328,6 +334,9 @@ object PermissionFlags { const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { if (flags.hasBits(PURPOSE_REVOKED)) { return false } if (flags.hasBits(INSTALL_GRANTED)) { return true } Loading Loading @@ -497,6 +506,7 @@ object PermissionFlags { if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) { flags = flags or USER_SELECTED } flags = flags or (oldFlags and PURPOSE_REVOKED) return flags } Loading Loading @@ -526,6 +536,7 @@ object PermissionFlags { ONE_TIME -> "ONE_TIME" HIBERNATION -> "HIBERNATION" USER_SELECTED -> "USER_SELECTED" PURPOSE_REVOKED -> "PURPOSE_REVOKED" else -> "0x${flag.toUInt().toString(16).uppercase()}" } Loading services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class AppIdPermissionPolicyPermissionStatesTest : BasePermissionPolicyTest() { @Test fun testEvaluatePermissionState_normalPermissionAlreadyGranted_remainsUnchanged() { val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED val oldFlags = PermissionFlags.INSTALL_GRANTED testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {} val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0) Loading services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +251 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.server.permission.test import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo import android.os.Build import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.permission.flags.Flags import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.permission.AppIdPermissionPolicy Loading Loading @@ -64,6 +68,253 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() { .isEqualTo(expectedNewFlags) } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurpose_permissionRevoked() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with no purpose the actual permission flags" + " ($actualFlags) should be install granted + purpose revoked ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isFalse() } @Test @DisableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeAndFlagDisabled_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "With purpose declaration flag disabled, after onPackageAdded() is called for appId" + " $APP_ID_0 that requests a purpose-required permission with no purpose, the" + " actual permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithInvalidPurpose_permissionRevoked() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, setOf(INVALID_PURPOSE) ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with invalid purpose, the actual permission flags" + " ($actualFlags) should be install granted + purpose revoked ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isFalse() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidAndInvalidPurpose_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, // At least one valid purpose is sufficient for the permission to be granted. setOf(VALID_PURPOSE, INVALID_PURPOSE), oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with valid and invalid purpose, the actual" + " permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidPurpose_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, setOf(VALID_PURPOSE), oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with valid purpose, the actual permission flags" + " ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeTargetingOldSdk_permissionGranted() { testOnPackageAddedForPurposeDeclaration( Build.VERSION_CODES.BAKLAVA, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with no purpose on an older SDK than Android C, the" + " actual permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidPurposeInAnotherPackage_permissionGranted() { // Set package1 with valid purpose, so it should be sufficient for any other package // sharing the same UID to pass the purpose validation check. val sharedUidPackage1 = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_1, targetSdkVersion = BUILD_VERSION_CODES_C, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf( PERMISSION_NAME_0 to mockParsedUsesPermission( PERMISSION_NAME_0, purposes = setOf(VALID_PURPOSE) ) ) ) ) addPackageState(sharedUidPackage1) testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for package $PACKAGE_NAME_0 / appId $APP_ID_0" + " that requests a purpose-required permission with no purpose, the actual" + " permission flags ($actualFlags) should be granted ($expectedNewFlags)" + " due to valid purpose declared package in $PACKAGE_NAME_1 / appId $APP_ID_0" ) .that(actualFlags) .isEqualTo(expectedNewFlags) } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeAndAnotherPackageTargetingOldSdk_permissionGranted() { // Set package1 targeting old SDK, so it should be sufficient for any other package // sharing the same UID to pass the purpose validation check. val sharedUidPackage1 = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_1, targetSdkVersion = Build.VERSION_CODES.BAKLAVA, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf(PERMISSION_NAME_0 to mockParsedUsesPermission(PERMISSION_NAME_0)) ) ) addPackageState(sharedUidPackage1) testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for package $PACKAGE_NAME_0 / appId $APP_ID_0 that" + " requests a purpose-required permission with no purpose, the actual permission" + " flags ($actualFlags) should be granted ($expectedNewFlags) due to" + " $PACKAGE_NAME_1 / appId $APP_ID_0 which targets an old SDK version." ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } private fun testOnPackageAddedForPurposeDeclaration( targetSdkVersion: Int, purposes: Set<String>, oldFlags: Int = PermissionFlags.INSTALL_GRANTED, ) { val addedPackage = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_0, targetSdkVersion = targetSdkVersion, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf( PERMISSION_NAME_0 to mockParsedUsesPermission(PERMISSION_NAME_0, purposes = purposes) ) ) ) val platformPackage = mockPackageState(PLATFORM_APP_ID, mockAndroidPackage(PLATFORM_PACKAGE_NAME)) addPackageState(platformPackage) addPackageState(addedPackage) addPermission(defaultPermissionWithValidPurpose) setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags) mutateState { with(appIdPermissionPolicy) { onPackageAdded(addedPackage) } } } @Test fun testOnStorageVolumeMounted_nonSystemAppAfterNonSystemUpdate_remainsRevoked() { val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) Loading Loading
services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +41 −11 Original line number Diff line number Diff line Loading @@ -986,9 +986,12 @@ class AppIdPermissionPolicy : SchemePolicy() { return } if (permission.isNormal) { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) var newFlags: Int val wasInstallGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) val wasInstallRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) if (wasInstallGranted || !wasInstallRevoked) { newFlags = PermissionFlags.INSTALL_GRANTED } else { val isRequestedByInstalledPackage = installedPackageState != null && permissionName in Loading @@ -1002,9 +1005,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform var newFlags = newFlags = if ( !wasRevoked || isRequestedByInstalledPackage || isRequestedBySystemPackage || isCompatibilityPermission Loading @@ -1013,13 +1015,25 @@ class AppIdPermissionPolicy : SchemePolicy() { } else { PermissionFlags.INSTALL_REVOKED } } // Starting from Android 17, an app requesting permission which requires purpose must // declare at least one valid purpose in its manifest before it can be granted. Note // that a flag state may have INSTALL_GRANTED and PURPOSE_REVOKED bits set, in which // case the permission will not be granted. if (Flags.purposeDeclarationEnabled() && permission.requiresPurpose) { val hasValidPurpose = requestingPackageStates.anyIndexed { _, it -> hasValidPurposeForPackage(it.androidPackage!!, permission) } if (!hasValidPurpose) { newFlags = newFlags or PermissionFlags.PURPOSE_REVOKED } } if (permission.isAppOp) { newFlags = newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) var newFlags = Loading Loading @@ -1307,6 +1321,22 @@ class AppIdPermissionPolicy : SchemePolicy() { return false } // TODO(b/422817717) - Handle app-compat scenarios by using versioning info at the purpose level private fun hasValidPurposeForPackage( androidPackage: AndroidPackage, permission: Permission, ): Boolean { // TODO(b/419394842) - Use < Android C build version code when available. if (androidPackage.targetSdkVersion <= Build.VERSION_CODES.BAKLAVA) { // Purpose declaration is not supported on older target versions. Bypass check. return true } val purposes = androidPackage.usesPermissionMapping[permission.name]?.purposes // There must be at least one valid purpose defined as the parsing logic would otherwise // force set the permission to not require purpose, making this code unreachable. return purposes?.any { it in permission.validPurposes } ?: false } private fun MutateStateScope.shouldGrantPermissionBySignature( packageState: PackageState, permission: Permission, Loading
services/permission/java/com/android/server/permission/access/permission/Permission.kt +6 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,12 @@ data class Permission( inline val knownCerts: Set<String> get() = permissionInfo.knownCerts inline val requiresPurpose: Boolean get() = permissionInfo.requiresPurpose inline val validPurposes: Set<String> get() = permissionInfo.validPurposes inline val hasGids: Boolean get() = gids.isNotEmpty() Loading
services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +11 −0 Original line number Diff line number Diff line Loading @@ -294,6 +294,12 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 /** * Permission flag for a normal permission that is revoked at install-time due to failing * purpose validation check. */ const val PURPOSE_REVOKED = 1 shl 24 /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() Loading Loading @@ -328,6 +334,9 @@ object PermissionFlags { const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { if (flags.hasBits(PURPOSE_REVOKED)) { return false } if (flags.hasBits(INSTALL_GRANTED)) { return true } Loading Loading @@ -497,6 +506,7 @@ object PermissionFlags { if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) { flags = flags or USER_SELECTED } flags = flags or (oldFlags and PURPOSE_REVOKED) return flags } Loading Loading @@ -526,6 +536,7 @@ object PermissionFlags { ONE_TIME -> "ONE_TIME" HIBERNATION -> "HIBERNATION" USER_SELECTED -> "USER_SELECTED" PURPOSE_REVOKED -> "PURPOSE_REVOKED" else -> "0x${flag.toUInt().toString(16).uppercase()}" } Loading
services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class AppIdPermissionPolicyPermissionStatesTest : BasePermissionPolicyTest() { @Test fun testEvaluatePermissionState_normalPermissionAlreadyGranted_remainsUnchanged() { val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED val oldFlags = PermissionFlags.INSTALL_GRANTED testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {} val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0) Loading
services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +251 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.server.permission.test import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo import android.os.Build import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.permission.flags.Flags import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.permission.AppIdPermissionPolicy Loading Loading @@ -64,6 +68,253 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() { .isEqualTo(expectedNewFlags) } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurpose_permissionRevoked() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with no purpose the actual permission flags" + " ($actualFlags) should be install granted + purpose revoked ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isFalse() } @Test @DisableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeAndFlagDisabled_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "With purpose declaration flag disabled, after onPackageAdded() is called for appId" + " $APP_ID_0 that requests a purpose-required permission with no purpose, the" + " actual permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithInvalidPurpose_permissionRevoked() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, setOf(INVALID_PURPOSE) ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with invalid purpose, the actual permission flags" + " ($actualFlags) should be install granted + purpose revoked ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isFalse() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidAndInvalidPurpose_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, // At least one valid purpose is sufficient for the permission to be granted. setOf(VALID_PURPOSE, INVALID_PURPOSE), oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with valid and invalid purpose, the actual" + " permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidPurpose_permissionGranted() { testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, setOf(VALID_PURPOSE), oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with valid purpose, the actual permission flags" + " ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeTargetingOldSdk_permissionGranted() { testOnPackageAddedForPurposeDeclaration( Build.VERSION_CODES.BAKLAVA, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for appId $APP_ID_0 that requests a" + " purpose-required permission with no purpose on an older SDK than Android C, the" + " actual permission flags ($actualFlags) should be granted ($expectedNewFlags)" ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithValidPurposeInAnotherPackage_permissionGranted() { // Set package1 with valid purpose, so it should be sufficient for any other package // sharing the same UID to pass the purpose validation check. val sharedUidPackage1 = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_1, targetSdkVersion = BUILD_VERSION_CODES_C, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf( PERMISSION_NAME_0 to mockParsedUsesPermission( PERMISSION_NAME_0, purposes = setOf(VALID_PURPOSE) ) ) ) ) addPackageState(sharedUidPackage1) testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for package $PACKAGE_NAME_0 / appId $APP_ID_0" + " that requests a purpose-required permission with no purpose, the actual" + " permission flags ($actualFlags) should be granted ($expectedNewFlags)" + " due to valid purpose declared package in $PACKAGE_NAME_1 / appId $APP_ID_0" ) .that(actualFlags) .isEqualTo(expectedNewFlags) } @Test @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED) fun testOnPackageAdded_requestsPermissionWithNoPurposeAndAnotherPackageTargetingOldSdk_permissionGranted() { // Set package1 targeting old SDK, so it should be sufficient for any other package // sharing the same UID to pass the purpose validation check. val sharedUidPackage1 = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_1, targetSdkVersion = Build.VERSION_CODES.BAKLAVA, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf(PERMISSION_NAME_0 to mockParsedUsesPermission(PERMISSION_NAME_0)) ) ) addPackageState(sharedUidPackage1) testOnPackageAddedForPurposeDeclaration( BUILD_VERSION_CODES_C, emptySet() ) val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) val expectedNewFlags = PermissionFlags.INSTALL_GRANTED assertWithMessage( "After onPackageAdded() is called for package $PACKAGE_NAME_0 / appId $APP_ID_0 that" + " requests a purpose-required permission with no purpose, the actual permission" + " flags ($actualFlags) should be granted ($expectedNewFlags) due to" + " $PACKAGE_NAME_1 / appId $APP_ID_0 which targets an old SDK version." ) .that(actualFlags) .isEqualTo(expectedNewFlags) assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue() } private fun testOnPackageAddedForPurposeDeclaration( targetSdkVersion: Int, purposes: Set<String>, oldFlags: Int = PermissionFlags.INSTALL_GRANTED, ) { val addedPackage = mockPackageState( APP_ID_0, mockAndroidPackage( PACKAGE_NAME_0, targetSdkVersion = targetSdkVersion, requestedPermissions = setOf(PERMISSION_NAME_0), usesPermissionMapping = mapOf( PERMISSION_NAME_0 to mockParsedUsesPermission(PERMISSION_NAME_0, purposes = purposes) ) ) ) val platformPackage = mockPackageState(PLATFORM_APP_ID, mockAndroidPackage(PLATFORM_PACKAGE_NAME)) addPackageState(platformPackage) addPackageState(addedPackage) addPermission(defaultPermissionWithValidPurpose) setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags) mutateState { with(appIdPermissionPolicy) { onPackageAdded(addedPackage) } } } @Test fun testOnStorageVolumeMounted_nonSystemAppAfterNonSystemUpdate_remainsRevoked() { val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) Loading