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

Commit 452188b4 authored by Yi-an Chen's avatar Yi-an Chen Committed by Android (Google) Code Review
Browse files

Merge "Add unit tests for AppIdPermissionPolicy" into main

parents dd593baa 891ef69f
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
    }
}