blob: 6c2a8916617b232a154d14c482649253474b54c7 [file] [log] [blame]
/*
* Copyright (C) 2021 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.pm.test.verify.domain
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.Signature
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainOwner
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED
import android.os.Build
import android.os.PatternMatcher
import android.os.Process
import android.util.ArraySet
import android.util.Xml
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.UUID
class DomainVerificationPackageTest {
companion object {
private const val PKG_ONE = "com.test.one"
private const val PKG_TWO = "com.test.two"
private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private const val SIGNATURE_ONE = "AA"
private const val DIGEST_ONE =
"BCEEF655B5A034911F1C3718CE056531B45EF03B4C7B1F15629E867294011A7D"
private const val SIGNATURE_TWO = "BB"
private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName
private val DOMAIN_1 = "one.$DOMAIN_BASE"
private val DOMAIN_2 = "two.$DOMAIN_BASE"
private val DOMAIN_3 = "three.$DOMAIN_BASE"
private val DOMAIN_4 = "four.$DOMAIN_BASE"
private const val USER_ID = 0
}
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO)
@Test
fun addPackageFirstTime() {
val service = makeService(pkg1, pkg2)
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageSystemConfigured() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, isSystemApp = false)
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO, isSystemApp = true)
val service = makeService(
systemConfiguredPackageNames = ArraySet(setOf(pkg1.getName(), pkg2.getName())),
pkg1, pkg2
)
service.addPackage(pkg1)
service.addPackage(pkg2)
service.getInfo(pkg1.getName()).apply {
assertThat(packageName).isEqualTo(pkg1.getName())
assertThat(identifier).isEqualTo(pkg1.domainSetId)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
}
service.getUserState(pkg1.getName()).apply {
assertThat(packageName).isEqualTo(pkg1.getName())
assertThat(identifier).isEqualTo(pkg1.domainSetId)
assertThat(isLinkHandlingAllowed).isEqualTo(true)
assertThat(user.identifier).isEqualTo(USER_ID)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
}
service.getInfo(pkg2.getName()).apply {
assertThat(packageName).isEqualTo(pkg2.getName())
assertThat(identifier).isEqualTo(pkg2.domainSetId)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_UNMODIFIABLE,
)
)
}
service.getUserState(pkg2.getName()).apply {
assertThat(packageName).isEqualTo(pkg2.getName())
assertThat(identifier).isEqualTo(pkg2.domainSetId)
assertThat(isLinkHandlingAllowed).isEqualTo(true)
assertThat(user.identifier).isEqualTo(USER_ID)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_VERIFIED,
)
)
}
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName(), pkg2.getName())
}
@Test
fun addPackageRestoredMatchingSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="$DIGEST_ONE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageRestoredMismatchSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="INVALID_SIGNATURE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageActive() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
>
<state>
<domain name="$DOMAIN_1" state="$STATE_SUCCESS"/>
</state>
<user-states>
<user-state userId="$USER_ID" allowLinkHandling="false">
<enabled-hosts>
<host name="$DOMAIN_2"/>
</enabled-hosts>
</user-state>
</user-states>
</package-state>
</active>
</domain-verifications>
""".trimIndent()
val service = makeService(pkg1, pkg2)
xml.byteInputStream().use {
service.readSettings(Xml.resolvePullParser(it))
}
service.addPackage(pkg1)
assertAddPackageActivePendingRestoredState(service)
}
@Test
fun addPackagePendingStripInvalidDomains() {
val xml = addPackagePendingOrRestoredWithInvalidDomains()
val service = makeService(pkg1, pkg2)
xml.byteInputStream().use {
service.readSettings(Xml.resolvePullParser(it))
}
service.addPackage(pkg1)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(false)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
assertAddPackageActivePendingRestoredState(service)
}
@Test
fun addPackageRestoredStripInvalidDomains() {
val xml = addPackagePendingOrRestoredWithInvalidDomains()
val service = makeService(pkg1, pkg2)
xml.byteInputStream().use {
service.restoreSettings(Xml.resolvePullParser(it))
}
service.addPackage(pkg1)
assertAddPackageActivePendingRestoredState(service, expectRestore = true)
}
/**
* Shared string that contains invalid [DOMAIN_3] and [DOMAIN_4] which should be stripped from
* the final state.
*/
private fun addPackagePendingOrRestoredWithInvalidDomains(): String =
// language=XML
"""
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="$DIGEST_ONE"
>
<state>
<domain name="$DOMAIN_1" state="$STATE_SUCCESS"/>
<domain name="$DOMAIN_3" state="$STATE_SUCCESS"/>
</state>
<user-states>
<user-state userId="$USER_ID" allowLinkHandling="false">
<enabled-hosts>
<host name="$DOMAIN_2"/>
<host name="$DOMAIN_4"/>
</enabled-hosts>
</user-state>
<user-state userId="${USER_ID + 10}" allowLinkHandling="true">
<enabled-hosts>
<host name="$DOMAIN_4"/>
</enabled-hosts>
</user-state>
</user-states>
</package-state>
</active>
</domain-verifications>
""".trimIndent()
/**
* Shared method to assert the same output when testing adding pkg1.
*/
private fun assertAddPackageActivePendingRestoredState(
service: DomainVerificationService,
expectRestore: Boolean = false
) {
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
// To share the majority of code, special case restoration to check a different int
DOMAIN_1 to if (expectRestore) STATE_MODIFIABLE_VERIFIED else STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
))
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(false)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
// Re-enable link handling to check that the 3/4 domains were stripped
service.setDomainVerificationLinkHandlingAllowed(pkg1.getName(), true, USER_ID)
assertThat(service.getOwnersForDomain(DOMAIN_1, USER_ID))
.containsExactly(DomainOwner(PKG_ONE, false))
assertThat(service.getOwnersForDomain(DOMAIN_2, USER_ID))
.containsExactly(DomainOwner(PKG_ONE, true))
assertThat(service.getOwnersForDomain(DOMAIN_2, USER_ID + 10)).isEmpty()
listOf(DOMAIN_3, DOMAIN_4).forEach { domain ->
listOf(USER_ID, USER_ID + 10).forEach { userId ->
assertThat(service.getOwnersForDomain(domain, userId)).isEmpty()
}
}
}
@Test
fun migratePackageDropDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
// Test 4 domains:
// 1 will be approved and preserved, 2 will be selected and preserved,
// 3 will be denied and dropped, 4 will be selected and dropped
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
// To test the approve/denial states, use the internal methods for this variant
service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_APPROVED,
ArraySet(setOf(DOMAIN_1)))
service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_DENIED,
ArraySet(setOf(DOMAIN_3)))
service.setUserSelection(
UUID_ONE, setOf(DOMAIN_2, DOMAIN_4), true, USER_ID)
// Check the verifier cannot change the shell approve/deny states
service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_3), STATE_SUCCESS)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_NO_RESPONSE,
DOMAIN_3 to STATE_UNMODIFIABLE,
DOMAIN_4 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE,
DOMAIN_4 to DOMAIN_STATE_SELECTED,
))
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
map[pkgName] = pkgAfter
service.migrateState(pkgBefore, pkgAfter)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun migratePackageDropAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, emptyList())
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
assertThat(service.queryValidVerificationPackageNames()).isEmpty()
}
@Test
fun migratePackageAddDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
// Test 3 domains:
// 1 will be verified and preserved, 2 will be selected and preserved,
// 3 will be new and default
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)
service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
DOMAIN_3 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun migratePackageAddAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, emptyList())
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
assertThat(service.queryValidVerificationPackageNames()).isEmpty()
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun backupAndRestore() {
// This test acts as a proxy for true user restore through PackageManager,
// as that's much harder to test for real.
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
val serviceBefore = makeService(pkg1, pkg2)
serviceBefore.addPackage(pkg1)
serviceBefore.addPackage(pkg2)
serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS)
serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.getName(), false, 10)
serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_3), true, 10)
fun assertExpectedState(service: DomainVerificationService) {
service.assertState(
pkg1, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
service.assertState(
pkg1, userId = 10, linkHandingAllowed = false, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
service.assertState(
pkg2, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE
)
)
service.assertState(
pkg2, userId = 10, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_SELECTED,
)
)
}
assertExpectedState(serviceBefore)
val backupUser0 = ByteArrayOutputStream().use {
serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0)
it.toByteArray()
}
val backupUser1 = ByteArrayOutputStream().use {
serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 10)
it.toByteArray()
}
val serviceAfter = makeService(pkg1, pkg2)
serviceAfter.addPackage(pkg1)
serviceAfter.addPackage(pkg2)
// Check the state is default before the restoration applies
listOf(0, 10).forEach {
serviceAfter.assertState(
pkg1, userId = it, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
}
listOf(0, 10).forEach {
serviceAfter.assertState(
pkg2, userId = it, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_NONE,
)
)
}
ByteArrayInputStream(backupUser1).use {
serviceAfter.restoreSettings(Xml.resolvePullParser(it))
}
// Assert user 1 was restored
serviceAfter.assertState(
pkg1, userId = 10, linkHandingAllowed = false, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
serviceAfter.assertState(
pkg2, userId = 10, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_SELECTED,
)
)
// User 0 has domain verified (since that's not user-specific)
serviceAfter.assertState(
pkg1, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
// But user 0 is missing any user selected state
serviceAfter.assertState(
pkg2, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_NONE,
)
)
ByteArrayInputStream(backupUser0).use {
serviceAfter.restoreSettings(Xml.resolvePullParser(it))
}
assertExpectedState(serviceAfter)
}
private fun DomainVerificationService.getInfo(pkgName: String) =
getDomainVerificationInfo(pkgName)
.also { assertThat(it).isNotNull() }!!
private fun DomainVerificationService.getUserState(pkgName: String, userId: Int = USER_ID) =
getDomainVerificationUserState(pkgName, userId)
.also { assertThat(it).isNotNull() }!!
private fun makeService(
systemConfiguredPackageNames: ArraySet<String> = ArraySet(),
vararg pkgSettings: PackageSetting
) = makeService(systemConfiguredPackageNames = systemConfiguredPackageNames) {
pkgName -> pkgSettings.find { pkgName == it.getName() }
}
private fun makeService(vararg pkgSettings: PackageSetting) =
makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
private fun makeService(
systemConfiguredPackageNames: ArraySet<String> = ArraySet(),
pkgSettingFunction: (String) -> PackageSetting? = { null }
) = DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
whenever(checkPermission(anyString(), anyInt(), anyInt())) {
PackageManager.PERMISSION_GRANTED
}
}, mockThrowOnUnmocked {
whenever(this.linkedApps) { systemConfiguredPackageNames }
}, mockThrowOnUnmocked {
whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(10)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
mockPackageSettings {
pkgSettingFunction(it)
}
})
}
private fun mockPkgSetting(
pkgName: String,
domainSetId: UUID,
signature: String,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
isSystemApp: Boolean = false
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf(
ParsedActivity().apply {
domains.forEach {
addIntent(
ParsedIntentInfo().apply {
autoVerify = true
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataScheme("http")
addDataScheme("https")
addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
addDataAuthority(it, null)
}
)
}
},
)
whenever(activities) { activityList }
}
whenever(getPkg()) { pkg }
whenever(getName()) { pkgName }
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(10)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
whenever(isSystem) { isSystemApp }
}
private fun DomainVerificationService.assertState(
pkg: PackageSetting,
userId: Int,
linkHandingAllowed: Boolean = true,
hostToStateMap: Map<String, Int>
) {
getUserState(pkg.getName(), userId).apply {
assertThat(this.packageName).isEqualTo(pkg.getName())
assertThat(this.identifier).isEqualTo(pkg.domainSetId)
assertThat(this.isLinkHandlingAllowed).isEqualTo(linkHandingAllowed)
assertThat(this.user.identifier).isEqualTo(userId)
assertThat(this.hostToStateMap).containsExactlyEntriesIn(hostToStateMap)
}
}
}