1 /* <lambda>null2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm.test.verify.domain 18 19 import android.content.UriRelativeFilter 20 import android.content.UriRelativeFilter.PATH 21 import android.content.UriRelativeFilterGroup 22 import android.content.UriRelativeFilterGroup.ACTION_ALLOW 23 import android.content.pm.verify.domain.DomainVerificationState 24 import android.os.PatternMatcher.PATTERN_LITERAL 25 import android.os.UserHandle 26 import android.util.ArrayMap 27 import android.util.SparseArray 28 import android.util.Xml 29 import com.android.modules.utils.TypedXmlPullParser 30 import com.android.modules.utils.TypedXmlSerializer 31 import com.android.server.pm.verify.domain.DomainVerificationPersistence 32 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState 33 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState 34 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap 35 import com.google.common.truth.Truth.assertThat 36 import com.google.common.truth.Truth.assertWithMessage 37 import org.junit.Rule 38 import org.junit.Test 39 import org.junit.rules.TemporaryFolder 40 import org.xmlpull.v1.XmlPullParser 41 import java.io.File 42 import java.nio.charset.StandardCharsets 43 import java.util.UUID 44 45 class DomainVerificationPersistenceTest { 46 47 companion object { 48 private val PKG_PREFIX = DomainVerificationPersistenceTest::class.java.`package`!!.name 49 50 internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply { 51 outputStream().use { 52 // This must use the binary serializer the mirror the production behavior, as 53 // there are slight differences with the string based one. 54 Xml.newBinarySerializer() 55 .apply { 56 setOutput(it, StandardCharsets.UTF_8.name()) 57 startDocument(null, true) 58 setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true) 59 // Write a wrapping tag to ensure the domain verification settings didn't 60 // close out the document, allowing other settings to be written 61 startTag(null, "wrapper-tag") 62 } 63 .apply(block) 64 .apply { 65 startTag(null, "trailing-tag") 66 endTag(null, "trailing-tag") 67 endTag(null, "wrapper-tag") 68 } 69 .endDocument() 70 } 71 } 72 73 internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) = 74 inputStream().use { 75 val parser = Xml.resolvePullParser(it) 76 assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) 77 assertThat(parser.name).isEqualTo("wrapper-tag") 78 assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) 79 block(parser).also { 80 assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) 81 assertThat(parser.name).isEqualTo("trailing-tag") 82 assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG) 83 assertThat(parser.name).isEqualTo("trailing-tag") 84 assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG) 85 assertThat(parser.name).isEqualTo("wrapper-tag") 86 } 87 } 88 } 89 90 @Rule 91 @JvmField 92 val tempFolder = TemporaryFolder() 93 94 private fun mockWriteValues( 95 pkgNameToSignature: (String) -> String? = { null } 96 ): Triple<DomainVerificationStateMap<DomainVerificationPkgState>, 97 ArrayMap<String, DomainVerificationPkgState>, 98 ArrayMap<String, DomainVerificationPkgState>> { 99 val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply { 100 mockPkgState(0, pkgNameToSignature).let { put(it.packageName, it.id, it) } 101 mockPkgState(1, pkgNameToSignature).let { put(it.packageName, it.id, it) } 102 } 103 val pending = ArrayMap<String, DomainVerificationPkgState>().apply { 104 mockPkgState(2, pkgNameToSignature).let { put(it.packageName, it) } 105 mockPkgState(3, pkgNameToSignature).let { put(it.packageName, it) } 106 } 107 val restored = ArrayMap<String, DomainVerificationPkgState>().apply { 108 mockPkgState(4, pkgNameToSignature).let { put(it.packageName, it) } 109 mockPkgState(5, pkgNameToSignature).let { put(it.packageName, it) } 110 } 111 112 return Triple(attached, pending, restored) 113 } 114 115 @Test 116 fun writeAndReadBackNormal() { 117 val (attached, pending, restored) = mockWriteValues() 118 val file = tempFolder.newFile().writeXml { 119 DomainVerificationPersistence.writeToXml(it, attached, pending, restored, 120 UserHandle.USER_ALL, null) 121 } 122 123 val xml = file.readText() 124 125 val (readActive, readRestored) = file.readXml { 126 DomainVerificationPersistence.readFromXml(it) 127 } 128 129 assertWithMessage(xml).that(readActive.values) 130 .containsExactlyElementsIn(attached.values() + pending.values) 131 assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values) 132 } 133 134 @Test 135 fun writeAndReadBackWithSignature() { 136 val (attached, pending, restored) = mockWriteValues() 137 val file = tempFolder.newFile().writeXml { 138 DomainVerificationPersistence.writeToXml(it, attached, pending, restored, 139 UserHandle.USER_ALL) { 140 "SIGNATURE_$it" 141 } 142 } 143 144 val (readActive, readRestored) = file.readXml { 145 DomainVerificationPersistence.readFromXml(it) 146 } 147 148 // Assign the signatures to a fresh set of data structures, to ensure the previous write 149 // call did not use the signatures from the data structure. This is because the method is 150 // intended to optionally append signatures, regardless of if the existing data structures 151 // contain them or not. 152 val (attached2, pending2, restored2) = mockWriteValues { "SIGNATURE_$it" } 153 154 assertThat(readActive.values) 155 .containsExactlyElementsIn(attached2.values() + pending2.values) 156 assertThat(readRestored.values).containsExactlyElementsIn(restored2.values) 157 158 (readActive + readRestored).forEach { (_, value) -> 159 assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}") 160 } 161 } 162 163 @Test 164 fun writeStateSignatureIfFunctionReturnsNull() { 165 val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" } 166 val file = tempFolder.newFile().writeXml { 167 DomainVerificationPersistence.writeToXml(it, attached, pending, restored, 168 UserHandle.USER_ALL) { null } 169 } 170 171 val (readActive, readRestored) = file.readXml { 172 DomainVerificationPersistence.readFromXml(it) 173 } 174 175 assertThat(readActive.values) 176 .containsExactlyElementsIn(attached.values() + pending.values) 177 assertThat(readRestored.values).containsExactlyElementsIn(restored.values) 178 179 (readActive + readRestored).forEach { (_, value) -> 180 assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}") 181 } 182 } 183 184 @Test 185 fun readMalformed() { 186 val stateZero = mockEmptyPkgState(0, pkgNameToSignature = { "ACTIVE" }).apply { 187 stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS 188 stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED 189 190 // A domain without a written state falls back to default 191 stateMap["missing-state.com"] = DomainVerificationState.STATE_NO_RESPONSE 192 193 userStates[1] = DomainVerificationInternalUserState(1).apply { 194 addHosts(setOf("example-user1.com", "example-user1.org")) 195 isLinkHandlingAllowed = true 196 } 197 } 198 val stateOne = mockEmptyPkgState(1, pkgNameToSignature = { "RESTORED" }).apply { 199 // It's valid to have a user selection without any autoVerify domains 200 userStates[1] = DomainVerificationInternalUserState(1).apply { 201 addHosts(setOf("example-user1.com", "example-user1.org")) 202 isLinkHandlingAllowed = false 203 } 204 } 205 206 // Also valid to have neither autoVerify domains nor any active user states 207 val stateTwo = mockEmptyPkgState(2, hasAutoVerifyDomains = false) 208 209 // language=XML 210 val xml = """ 211 <?xml?> 212 <domain-verifications> 213 <active> 214 <package-state 215 packageName="${stateZero.packageName}" 216 id="${stateZero.id}" 217 > 218 <state> 219 <domain name="duplicate-takes-last.com" state="1"/> 220 </state> 221 </package-state> 222 <package-state 223 packageName="${stateZero.packageName}" 224 id="${stateZero.id}" 225 hasAutoVerifyDomains="true" 226 signature="ACTIVE" 227 > 228 <state> 229 <domain name="example.com" state="${ 230 DomainVerificationState.STATE_SUCCESS}"/> 231 <domain name="example.org" state="${ 232 DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED}"/> 233 <not-domain name="not-domain.com" state="1"/> 234 <domain name="missing-state.com"/> 235 </state> 236 <user-states> 237 <user-state userId="1" allowLinkHandling="true"> 238 <enabled-hosts> 239 <host name="example-user1.com"/> 240 <not-host name="not-host.com"/> 241 <host/> 242 </enabled-hosts> 243 <enabled-hosts> 244 <host name="example-user1.org"/> 245 </enabled-hosts> 246 <enabled-hosts/> 247 </user-state> 248 <user-state> 249 <enabled-hosts> 250 <host name="no-user-id.com"/> 251 </enabled-hosts> 252 </user-state> 253 </user-states> 254 </package-state> 255 </active> 256 <not-active/> 257 <restored> 258 <package-state 259 packageName="${stateOne.packageName}" 260 id="${stateOne.id}" 261 hasAutoVerifyDomains="true" 262 signature="RESTORED" 263 > 264 <state/> 265 <user-states> 266 <user-state userId="1"> 267 <enabled-hosts> 268 <host name="example-user1.com"/> 269 <host name="example-user1.org"/> 270 </enabled-hosts> 271 </user-state> 272 </user-states> 273 </package-state> 274 <package-state packageName="${stateTwo.packageName}"/> 275 <package-state id="${stateTwo.id}"/> 276 <package-state 277 packageName="${stateTwo.packageName}" 278 id="${stateTwo.id}" 279 hasAutoVerifyDomains="false" 280 > 281 <state/> 282 <user-states/> 283 </package-state> 284 </restore> 285 <not-restored/> 286 </domain-verifications> 287 """.trimIndent() 288 289 val (active, restored) = DomainVerificationPersistence 290 .readFromXml(Xml.resolvePullParser(xml.byteInputStream())) 291 292 assertThat(active.values).containsExactly(stateZero) 293 assertThat(restored.values).containsExactly(stateOne, stateTwo) 294 } 295 296 private fun mockEmptyPkgState( 297 id: Int, 298 hasAutoVerifyDomains: Boolean = true, 299 pkgNameToSignature: (String) -> String? = { null } 300 ): DomainVerificationPkgState { 301 val pkgName = pkgName(id) 302 val domainSetId = UUID(0L, id.toLong()) 303 return DomainVerificationPkgState( 304 pkgName, 305 domainSetId, 306 hasAutoVerifyDomains, 307 ArrayMap(), 308 SparseArray(), 309 pkgNameToSignature(pkgName) 310 ) 311 } 312 313 private fun mockPkgState(id: Int, pkgNameToSignature: (String) -> String? = { null }) = 314 mockEmptyPkgState(id, pkgNameToSignature = pkgNameToSignature) 315 .apply { 316 stateMap["$packageName.com"] = id 317 userStates[id] = DomainVerificationInternalUserState(id).apply { 318 addHosts(setOf("$packageName-user.com")) 319 isLinkHandlingAllowed = true 320 } 321 val group = UriRelativeFilterGroup(ACTION_ALLOW) 322 group.addUriRelativeFilter(UriRelativeFilter(PATH, PATTERN_LITERAL, "test")) 323 uriRelativeFilterGroupMap.put("example.com", listOf(group)) 324 } 325 326 private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id" 327 } 328