1 /*
2  * 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.permissioncontroller.tests.mocking.role.model
18 
19 import android.app.AppOpsManager
20 import android.content.pm.PackageManager
21 import android.content.pm.PermissionInfo
22 import android.os.Process
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.platform.app.InstrumentationRegistry
25 import com.android.permissioncontroller.role.model.RoleParserInitializer
26 import com.android.role.controller.model.AppOp
27 import com.android.role.controller.model.Permission
28 import com.android.role.controller.model.PermissionSet
29 import com.android.role.controller.model.Role
30 import com.android.role.controller.model.RoleParser
31 import org.junit.BeforeClass
32 import org.junit.Test
33 import org.junit.runner.RunWith
34 
35 @RunWith(AndroidJUnit4::class)
36 class RoleParserTest {
37     companion object {
38         @BeforeClass
39         @JvmStatic
setupBeforeClassnull40         fun setupBeforeClass() {
41             RoleParserInitializer.initialize()
42         }
43     }
44 
45     private val instrumentation = InstrumentationRegistry.getInstrumentation()
46     private val uiAutomation = instrumentation.uiAutomation
47     private val targetContext = instrumentation.targetContext
48     private val packageManager = targetContext.packageManager
49 
50     @Test
parseRolesnull51     fun parseRoles() {
52         // We may need to call privileged APIs to determine availability of things.
53         uiAutomation.adoptShellPermissionIdentity()
54         try {
55             RoleParser(targetContext, true).parse()
56         } finally {
57             uiAutomation.dropShellPermissionIdentity()
58         }
59     }
60 
61     @Test
validateRolesnull62     fun validateRoles() {
63         // We may need to call privileged APIs to determine availability of things.
64         uiAutomation.adoptShellPermissionIdentity()
65         try {
66             val xml = RoleParser(targetContext).parseRolesXml()
67             requireNotNull(xml)
68             validateRoles(xml.first, xml.second)
69         } finally {
70             uiAutomation.dropShellPermissionIdentity()
71         }
72     }
73 
validateRolesnull74     fun validateRoles(permissionSets: Map<String, PermissionSet>, roles: Map<String, Role>) {
75         for (permissionSet in permissionSets.values) {
76             for (permission in permissionSet.permissions) {
77                 validatePermission(permission)
78             }
79         }
80 
81         for (role in roles.values) {
82             if (!role.isAvailableByFeatureFlagAndSdkVersion) {
83                 continue
84             }
85 
86             for (requiredComponent in role.requiredComponents) {
87                 val permission = requiredComponent.permission
88                 if (permission != null) {
89                     validatePermission(permission)
90                 }
91             }
92 
93             for (permission in role.permissions) {
94                 validatePermission(permission)
95             }
96 
97             for (appOp in role.appOps) {
98                 validateAppOp(appOp)
99             }
100 
101             for (appOpPermission in role.appOpPermissions) {
102                 validateAppOpPermission(appOpPermission)
103             }
104 
105             for (preferredActivity in role.preferredActivities) {
106                 require(preferredActivity.activity in role.requiredComponents) {
107                     "<activity> of <preferred-activity> not required in <required-components>," +
108                         " role: ${role.name}, preferred activity: $preferredActivity"
109                 }
110             }
111         }
112     }
113 
validatePermissionnull114     private fun validatePermission(permission: Permission) {
115         if (!permission.isAvailableAsUser(Process.myUserHandle(), targetContext)) {
116             return
117         }
118         validatePermission(permission.name, true)
119     }
120 
validatePermissionnull121     private fun validatePermission(permissionName: String) {
122         validatePermission(permissionName, false)
123     }
124 
validatePermissionnull125     private fun validatePermission(permissionName: String, enforceIsRuntimeOrRole: Boolean) {
126         val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
127         // Skip validation for car permissions which may not be available on all build targets.
128         if (!isAutomotive && permissionName.startsWith("android.car")) {
129             return
130         }
131 
132         val permissionInfo =
133             try {
134                 packageManager.getPermissionInfo(permissionName, 0)
135             } catch (e: PackageManager.NameNotFoundException) {
136                 throw IllegalArgumentException("Unknown permission: $permissionName", e)
137             }
138 
139         if (enforceIsRuntimeOrRole) {
140             require(
141                 permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS ||
142                     permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_ROLE ==
143                         PermissionInfo.PROTECTION_FLAG_ROLE
144             ) {
145                 "Permission is not a runtime or role permission: $permissionName"
146             }
147         }
148     }
149 
validateAppOpPermissionnull150     private fun validateAppOpPermission(appOpPermission: Permission) {
151         if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), targetContext)) {
152             return
153         }
154         validateAppOpPermission(appOpPermission.name)
155     }
156 
validateAppOpPermissionnull157     private fun validateAppOpPermission(permissionName: String) {
158         val permissionInfo =
159             try {
160                 packageManager.getPermissionInfo(permissionName, 0)
161             } catch (e: PackageManager.NameNotFoundException) {
162                 throw IllegalArgumentException("Unknown app op permission: $permissionName", e)
163             }
164         require(
165             permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_APPOP ==
166                 PermissionInfo.PROTECTION_FLAG_APPOP
167         ) {
168             "Permission is not an app op permission: $permissionName"
169         }
170     }
171 
validateAppOpnull172     private fun validateAppOp(appOp: AppOp) {
173         if (!appOp.isAvailableByFeatureFlagAndSdkVersion) {
174             return
175         }
176         // This throws IllegalArgumentException if app op is unknown.
177         val permissionName = AppOpsManager.opToPermission(appOp.name)
178         if (permissionName != null) {
179             val permissionInfo = packageManager.getPermissionInfo(permissionName, 0)
180             require(permissionInfo.protection != PermissionInfo.PROTECTION_DANGEROUS) {
181                 "App op has an associated runtime permission: ${appOp.name}"
182             }
183         }
184     }
185 }
186