Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 891ef69f authored by Yi-an Chen's avatar Yi-an Chen
Browse files

Add unit tests for AppIdPermissionPolicy

Add tests for onPackageUninstalled(), onStorageVolumeAdded() and
onUserAdded() for AppIdPermissionPolicy

These functions have some similar logic to the previously added
onPackageAdded() and resetRuntimePermissions(). Hence refactored and
parameterized the tests.

Bug: 280853174
Test: AppIdPermissionPolicyTest
Change-Id: Id21aded723e454710a4fdc0d15e5f555524341a2
parent 99c86797
Loading
Loading
Loading
Loading
+764 −0

File added.

Preview size limit exceeded, changes collapsed.

+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.server.permission.test

import android.content.pm.PermissionInfo
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.permission.PermissionFlags
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

/**
 * A parameterized test for testing resetting runtime permissions for onPackageUninstalled()
 * and resetRuntimePermissions() in AppIdPermissionPolicy
 */
@RunWith(Parameterized::class)
class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() {
    @Parameterized.Parameter(0) lateinit var action: Action

    @Test
    fun testResetRuntimePermissions_runtimeGranted_getsRevoked() {
        val oldFlags = PermissionFlags.RUNTIME_GRANTED
        val expectedNewFlags = 0
        testResetRuntimePermissions(oldFlags, expectedNewFlags)
    }

    @Test
    fun testResetRuntimePermissions_roleGranted_getsGranted() {
        val oldFlags = PermissionFlags.ROLE
        val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED
        testResetRuntimePermissions(oldFlags, expectedNewFlags)
    }

    @Test
    fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() {
        val oldFlags = PermissionFlags.RUNTIME_GRANTED
        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
        testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true)
    }

    private fun testResetRuntimePermissions(
        oldFlags: Int,
        expectedNewFlags: Int,
        isAndroidPackageMissing: Boolean = false
    ) {
        val parsedPermission = mockParsedPermission(
            PERMISSION_NAME_0,
            PACKAGE_NAME_0,
            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
        )
        val permissionOwnerPackageState = mockPackageState(
            APP_ID_0,
            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
        )
        val requestingPackageState = if (isAndroidPackageMissing) {
            mockPackageState(APP_ID_1, PACKAGE_NAME_1)
        } else {
            mockPackageState(
                APP_ID_1,
                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
            )
        }
        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
        addPackageState(permissionOwnerPackageState)
        addPackageState(requestingPackageState)
        addPermission(parsedPermission)

        mutateState { testAction() }

        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
        assertWithMessage(
            "After resetting runtime permissions, permission flags did not match" +
                " expected values: expectedNewFlags is $expectedNewFlags," +
                " actualFlags is $actualFlags, while the oldFlags is $oldFlags"
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)
    }

    private fun MutateStateScope.testAction(
        packageName: String = PACKAGE_NAME_1,
        appId: Int = APP_ID_1,
        userId: Int = USER_ID_0
    ) {
        with(appIdPermissionPolicy) {
            when (action) {
                Action.ON_PACKAGE_UNINSTALLED -> onPackageUninstalled(packageName, appId, userId)
                Action.RESET_RUNTIME_PERMISSIONS -> resetRuntimePermissions(packageName, userId)
            }
        }
    }

    enum class Action { ON_PACKAGE_UNINSTALLED, RESET_RUNTIME_PERMISSIONS }

    companion object {
        @Parameterized.Parameters(name = "{0}")
        @JvmStatic
        fun data(): Array<Action> = Action.values()
    }
}
 No newline at end of file
+884 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

+446 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.server.permission.test

import android.Manifest
import android.content.pm.PackageManager
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.os.Build
import android.os.Bundle
import android.util.ArrayMap
import android.util.SparseArray
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutableUserState
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.AppIdPermissionPolicy
import com.android.server.permission.access.permission.Permission
import com.android.server.permission.access.permission.PermissionFlags
import com.android.server.permission.access.util.hasBits
import com.android.server.pm.parsing.PackageInfoUtils
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageState
import com.android.server.pm.pkg.PackageUserState
import com.android.server.pm.pkg.component.ParsedPermission
import com.android.server.pm.pkg.component.ParsedPermissionGroup
import com.android.server.testutils.any
import com.android.server.testutils.mock
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyLong

/**
 * Mocking unit test for AppIdPermissionPolicy.
 */
@RunWith(AndroidJUnit4::class)
open class BaseAppIdPermissionPolicyTest {
    protected lateinit var oldState: MutableAccessState
    protected lateinit var newState: MutableAccessState

    protected val defaultPermissionGroup = mockParsedPermissionGroup(
        PERMISSION_GROUP_NAME_0,
        PACKAGE_NAME_0
    )
    protected val defaultPermissionTree = mockParsedPermission(
        PERMISSION_TREE_NAME,
        PACKAGE_NAME_0,
        isTree = true
    )
    protected val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)

    protected val appIdPermissionPolicy = AppIdPermissionPolicy()

    @Rule
    @JvmField
    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
        .spyStatic(PackageInfoUtils::class.java)
        .build()

    @Before
    open fun setUp() {
        oldState = MutableAccessState()
        createUserState(USER_ID_0)
        oldState.mutateExternalState().setPackageStates(ArrayMap())
        oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap())
        mockPackageInfoUtilsGeneratePermissionInfo()
        mockPackageInfoUtilsGeneratePermissionGroupInfo()
    }

    protected fun createUserState(userId: Int) {
        oldState.mutateExternalState().mutateUserIds().add(userId)
        oldState.mutateUserStatesNoWrite().put(userId, MutableUserState())
    }

    private fun mockPackageInfoUtilsGeneratePermissionInfo() {
        wheneverStatic {
            PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong())
        }.thenAnswer { invocation ->
            val parsedPermission = invocation.getArgument<ParsedPermission>(0)
            val generateFlags = invocation.getArgument<Long>(1)
            PermissionInfo(parsedPermission.backgroundPermission).apply {
                name = parsedPermission.name
                packageName = parsedPermission.packageName
                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
                    parsedPermission.metaData
                } else {
                    null
                }
                @Suppress("DEPRECATION")
                protectionLevel = parsedPermission.protectionLevel
                group = parsedPermission.group
                flags = parsedPermission.flags
            }
        }
    }

    private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() {
        wheneverStatic {
            PackageInfoUtils.generatePermissionGroupInfo(
                any(ParsedPermissionGroup::class.java),
                anyLong()
            )
        }.thenAnswer { invocation ->
            val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0)
            val generateFlags = invocation.getArgument<Long>(1)
            @Suppress("DEPRECATION")
            PermissionGroupInfo().apply {
                name = parsedPermissionGroup.name
                packageName = parsedPermissionGroup.packageName
                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
                    parsedPermissionGroup.metaData
                } else {
                    null
                }
                flags = parsedPermissionGroup.flags
            }
        }
    }

    @Test
    fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
        val permissionOwnerPackageState = mockPackageState(
            APP_ID_0,
            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
        )
        val requestingPackageState = mockPackageState(
            APP_ID_1,
            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
        )
        addPackageState(permissionOwnerPackageState)
        addPackageState(requestingPackageState)
        addPermission(parsedPermission)
        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED)

        mutateState {
            with(appIdPermissionPolicy) {
                onAppIdRemoved(APP_ID_1)
            }
        }

        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = 0
        assertWithMessage(
            "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" +
                " owns by appId $APP_ID_0 with existing permission flags. The actual permission" +
                " flags $actualFlags should be null"
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)
    }

    @Test
    fun testOnPackageRemoved_packageIsRemoved_permissionsAreTrimmedAndStatesAreEvaluated() {
        // TODO
        // shouldn't reuse test cases because it's really different despite it's also for
        // trim permission states. It's different because it's package removal
    }

    @Test
    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
        // TODO
        // should be fine for it to be its own test cases and not to re-use
        // clearRestrictedPermissionImplicitExemption
    }

    @Test
    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
        // TODO
    }

    @Test
    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
        // TODO
    }

    @Test
    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
        // TODO
    }

    @Test
    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
        // TODO
    }

    @Test
    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
        // TODO
    }

    /**
     * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
     */
    protected fun mockSimpleAndroidPackage(): AndroidPackage =
        mockAndroidPackage(
            PACKAGE_NAME_0,
            permissionGroups = listOf(defaultPermissionGroup),
            permissions = listOf(defaultPermissionTree, defaultPermission)
        )

    protected inline fun mutateState(action: MutateStateScope.() -> Unit) {
        newState = oldState.toMutable()
        MutateStateScope(oldState, newState).action()
    }

    protected fun mockPackageState(
        appId: Int,
        packageName: String,
        isSystem: Boolean = false,
    ): PackageState =
        mock {
            whenever(this.appId).thenReturn(appId)
            whenever(this.packageName).thenReturn(packageName)
            whenever(androidPackage).thenReturn(null)
            whenever(this.isSystem).thenReturn(isSystem)
        }

    protected fun mockPackageState(
        appId: Int,
        androidPackage: AndroidPackage,
        isSystem: Boolean = false,
        isPrivileged: Boolean = false,
        isProduct: Boolean = false,
        isInstantApp: Boolean = false,
        isVendor: Boolean = false
    ): PackageState =
        mock {
            whenever(this.appId).thenReturn(appId)
            whenever(this.androidPackage).thenReturn(androidPackage)
            val packageName = androidPackage.packageName
            whenever(this.packageName).thenReturn(packageName)
            whenever(this.isSystem).thenReturn(isSystem)
            whenever(this.isPrivileged).thenReturn(isPrivileged)
            whenever(this.isProduct).thenReturn(isProduct)
            whenever(this.isVendor).thenReturn(isVendor)
            val userStates = SparseArray<PackageUserState>().apply {
                put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) })
            }
            whenever(this.userStates).thenReturn(userStates)
        }

    protected fun mockAndroidPackage(
        packageName: String,
        targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
        isRequestLegacyExternalStorage: Boolean = false,
        adoptPermissions: List<String> = emptyList(),
        implicitPermissions: Set<String> = emptySet(),
        requestedPermissions: Set<String> = emptySet(),
        permissionGroups: List<ParsedPermissionGroup> = emptyList(),
        permissions: List<ParsedPermission> = emptyList(),
        isSignatureMatching: Boolean = false
    ): AndroidPackage =
        mock {
            whenever(this.packageName).thenReturn(packageName)
            whenever(this.targetSdkVersion).thenReturn(targetSdkVersion)
            whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage)
            whenever(this.adoptPermissions).thenReturn(adoptPermissions)
            whenever(this.implicitPermissions).thenReturn(implicitPermissions)
            whenever(this.requestedPermissions).thenReturn(requestedPermissions)
            whenever(this.permissionGroups).thenReturn(permissionGroups)
            whenever(this.permissions).thenReturn(permissions)
            val signingDetails = mock<SigningDetails> {
                whenever(
                    hasCommonSignerWithCapability(any(), any())
                ).thenReturn(isSignatureMatching)
                whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching)
                whenever(
                    checkCapability(any<SigningDetails>(), any())
                ).thenReturn(isSignatureMatching)
            }
            whenever(this.signingDetails).thenReturn(signingDetails)
        }

    protected fun mockParsedPermission(
        permissionName: String,
        packageName: String,
        backgroundPermission: String? = null,
        group: String? = null,
        protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL,
        flags: Int = 0,
        isTree: Boolean = false
    ): ParsedPermission =
        mock {
            whenever(name).thenReturn(permissionName)
            whenever(this.packageName).thenReturn(packageName)
            whenever(metaData).thenReturn(Bundle())
            whenever(this.backgroundPermission).thenReturn(backgroundPermission)
            whenever(this.group).thenReturn(group)
            whenever(this.protectionLevel).thenReturn(protectionLevel)
            whenever(this.flags).thenReturn(flags)
            whenever(this.isTree).thenReturn(isTree)
        }

    protected fun mockParsedPermissionGroup(
        permissionGroupName: String,
        packageName: String,
    ): ParsedPermissionGroup =
        mock {
            whenever(name).thenReturn(permissionGroupName)
            whenever(this.packageName).thenReturn(packageName)
            whenever(metaData).thenReturn(Bundle())
        }

    protected fun addPackageState(
        packageState: PackageState,
        state: MutableAccessState = oldState
    ) {
        state.mutateExternalState().apply {
            setPackageStates(
                packageStates.toMutableMap().apply {
                    put(packageState.packageName, packageState)
                }
            )
            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
                .add(packageState.packageName)
        }
    }

    protected fun addDisabledSystemPackageState(
        packageState: PackageState,
        state: MutableAccessState = oldState
    ) = state.mutateExternalState().apply {
        (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState
    }

    protected fun addPermission(
        parsedPermission: ParsedPermission,
        type: Int = Permission.TYPE_MANIFEST,
        isReconciled: Boolean = true,
        state: MutableAccessState = oldState
    ) {
        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
            parsedPermission,
            PackageManager.GET_META_DATA.toLong()
        )!!
        val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId
        val permission = Permission(permissionInfo, isReconciled, type, appId)
        if (parsedPermission.isTree) {
            state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
        } else {
            state.mutateSystemState().mutatePermissions()[permission.name] = permission
        }
    }

    protected fun addPermissionGroup(
        parsedPermissionGroup: ParsedPermissionGroup,
        state: MutableAccessState = oldState
    ) {
        state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] =
            PackageInfoUtils.generatePermissionGroupInfo(
                parsedPermissionGroup,
                PackageManager.GET_META_DATA.toLong()
            )!!
    }

    protected fun getPermission(
        permissionName: String,
        state: MutableAccessState = newState
    ): Permission? = state.systemState.permissions[permissionName]

    protected fun getPermissionTree(
        permissionTreeName: String,
        state: MutableAccessState = newState
    ): Permission? = state.systemState.permissionTrees[permissionTreeName]

    protected fun getPermissionGroup(
        permissionGroupName: String,
        state: MutableAccessState = newState
    ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName]

    protected fun getPermissionFlags(
        appId: Int,
        userId: Int,
        permissionName: String,
        state: MutableAccessState = newState
    ): Int =
        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)

    protected fun setPermissionFlags(
        appId: Int,
        userId: Int,
        permissionName: String,
        flags: Int,
        state: MutableAccessState = oldState
    ) =
        state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) {
            MutableIndexedMap()
        }.put(permissionName, flags)

    companion object {
        @JvmStatic protected val PACKAGE_NAME_0 = "packageName0"
        @JvmStatic protected val PACKAGE_NAME_1 = "packageName1"
        @JvmStatic protected val PACKAGE_NAME_2 = "packageName2"
        @JvmStatic protected val MISSING_ANDROID_PACKAGE = "missingAndroidPackage"
        @JvmStatic protected val PLATFORM_PACKAGE_NAME = "android"

        @JvmStatic protected val APP_ID_0 = 0
        @JvmStatic protected val APP_ID_1 = 1
        @JvmStatic protected val PLATFORM_APP_ID = 2

        @JvmStatic protected val PERMISSION_GROUP_NAME_0 = "permissionGroupName0"
        @JvmStatic protected val PERMISSION_GROUP_NAME_1 = "permissionGroupName1"

        @JvmStatic protected val PERMISSION_TREE_NAME = "permissionTree"

        @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0"
        @JvmStatic protected val PERMISSION_NAME_1 = "permissionName1"
        @JvmStatic protected val PERMISSION_NAME_2 = "permissionName2"
        @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE =
            Manifest.permission.READ_EXTERNAL_STORAGE
        @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS =
            Manifest.permission.POST_NOTIFICATIONS
        @JvmStatic protected val PERMISSION_BLUETOOTH_CONNECT =
            Manifest.permission.BLUETOOTH_CONNECT
        @JvmStatic protected val PERMISSION_ACCESS_BACKGROUND_LOCATION =
            Manifest.permission.ACCESS_BACKGROUND_LOCATION
        @JvmStatic protected val PERMISSION_ACCESS_MEDIA_LOCATION =
            Manifest.permission.ACCESS_MEDIA_LOCATION

        @JvmStatic protected val USER_ID_0 = 0
        @JvmStatic protected val USER_ID_NEW = 1
    }
}