1 /*
2  * Copyright (C) 2021 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.bedstead.nene.permissions;
18 
19 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
20 
21 import android.app.UiAutomation;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PermissionInfo;
25 import android.os.Build;
26 import android.util.Log;
27 
28 import com.android.bedstead.nene.TestApis;
29 import com.android.bedstead.nene.exceptions.NeneException;
30 import com.android.bedstead.nene.packages.Package;
31 import com.android.bedstead.nene.packages.PackageReference;
32 import com.android.bedstead.nene.users.UserReference;
33 import com.android.bedstead.nene.utils.ShellCommandUtils;
34 import com.android.bedstead.nene.utils.Versions;
35 
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40 
41 /** Permission manager for tests. */
42 public class Permissions {
43 
44     public static final String MANAGE_PROFILE_AND_DEVICE_OWNERS =
45             "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS";
46     public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
47     public static final String NOTIFY_PENDING_SYSTEM_UPDATE =
48             "android.permission.NOTIFY_PENDING_SYSTEM_UPDATE";
49 
50     private static final String LOG_TAG = "Permissions";
51 
52     private List<PermissionContextImpl> mPermissionContexts = new ArrayList<>();
53     private static final TestApis sTestApis = new TestApis();
54     private static final Context sContext = sTestApis.context().instrumentedContext();
55     private static final PackageManager sPackageManager = sContext.getPackageManager();
56     private static final PackageReference sInstrumentedPackage =
57             sTestApis.packages().find(sContext.getPackageName());
58     private static final UserReference sUser = sTestApis.users().instrumented();
59     private static final Package sShellPackage =
60             sTestApis.packages().find("com.android.shell").resolve();
61     private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS =
62             Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
63 
64     // Permissions is a singleton as permission state must be application wide
65     public static final Permissions sInstance = new Permissions();
66 
67     private Set<String> mExistingPermissions;
68 
Permissions()69     private Permissions() {
70 
71     }
72 
73     /**
74      * Enter a {@link PermissionContext} where the given permissions are granted.
75      *
76      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
77      * thrown.
78      *
79      * <p>Recommended usage:
80      * {@code
81      *
82      * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) {
83      *     // Code which needs the permissions goes here
84      * }
85      * }
86      */
withPermission(String... permissions)87     public PermissionContextImpl withPermission(String... permissions) {
88         if (mPermissionContexts.isEmpty()) {
89             recordExistingPermissions();
90         }
91 
92         PermissionContextImpl permissionContext = new PermissionContextImpl(this);
93         mPermissionContexts.add(permissionContext);
94 
95         permissionContext.withPermission(permissions);
96 
97         return permissionContext;
98     }
99 
100     /**
101      * Enter a {@link PermissionContext} where the given permissions are not granted.
102      *
103      * <p>If the permissions cannot be denied, and are not already denied, an exception will be
104      * thrown.
105      *
106      * <p>Recommended usage:
107      * {@code
108      *
109      * try (PermissionContext p =
110      *         mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) {
111      *     // Code which needs the permissions goes here
112      * }
113      */
withoutPermission(String... permissions)114     public PermissionContextImpl withoutPermission(String... permissions) {
115         if (mPermissionContexts.isEmpty()) {
116             recordExistingPermissions();
117         }
118 
119         PermissionContextImpl permissionContext = new PermissionContextImpl(this);
120         mPermissionContexts.add(permissionContext);
121 
122         permissionContext.withoutPermission(permissions);
123 
124         return permissionContext;
125     }
126 
undoPermission(PermissionContext permissionContext)127     void undoPermission(PermissionContext permissionContext) {
128         mPermissionContexts.remove(permissionContext);
129         applyPermissions();
130     }
131 
applyPermissions()132     void applyPermissions() {
133         if (mPermissionContexts.isEmpty()) {
134             restoreExistingPermissions();
135             return;
136         }
137 
138         Package resolvedInstrumentedPackage = sInstrumentedPackage.resolve();
139 
140         if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
141             ShellCommandUtils.uiAutomation().dropShellPermissionIdentity();
142         }
143         Set<String> grantedPermissions = new HashSet<>();
144         Set<String> deniedPermissions = new HashSet<>();
145 
146         for (PermissionContextImpl permissionContext : mPermissionContexts) {
147             for (String permission : permissionContext.grantedPermissions()) {
148                 grantedPermissions.add(permission);
149                 deniedPermissions.remove(permission);
150             }
151 
152             for (String permission : permissionContext.deniedPermissions()) {
153                 grantedPermissions.remove(permission);
154                 deniedPermissions.add(permission);
155             }
156         }
157 
158         Log.d(LOG_TAG, "Applying permissions granting: "
159                 + grantedPermissions + " denying: " + deniedPermissions);
160 
161         // We first try to use shell permissions, because they can be revoked/etc. much more easily
162 
163         Set<String> adoptedShellPermissions = new HashSet<>();
164 
165         for (String permission : grantedPermissions) {
166             Log.d(LOG_TAG , "Trying to grant " + permission);
167             if (resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
168                 // Already granted, can skip
169                 Log.d(LOG_TAG, permission + " already granted at runtime");
170             } else if (resolvedInstrumentedPackage.requestedPermissions().contains(permission)
171                     && sContext.checkSelfPermission(permission) == PERMISSION_GRANTED) {
172                 // Already granted, can skip
173                 Log.d(LOG_TAG, permission + " already granted from manifest");
174             } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
175                     && sShellPackage.requestedPermissions().contains(permission)) {
176                 adoptedShellPermissions.add(permission);
177                 Log.d(LOG_TAG, "will adopt " + permission);
178             } else if (canGrantPermission(permission)) {
179                 Log.d(LOG_TAG, "Granting " + permission);
180                 sInstrumentedPackage.grantPermission(sUser, permission);
181             } else {
182                 Log.d(LOG_TAG, "Can not grant " + permission);
183                 removePermissionContextsUntilCanApply();
184                 throw new NeneException("PermissionContext requires granting "
185                         + permission + " but cannot.");
186             }
187         }
188 
189         for (String permission : deniedPermissions) {
190             Log.d(LOG_TAG , "Trying to deny " + permission);
191             if (!resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
192                 // Already denied, can skip
193                 Log.d(LOG_TAG, permission + " already denied");
194             } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
195                     && !sShellPackage.requestedPermissions().contains(permission)) {
196                 adoptedShellPermissions.add(permission);
197                 Log.d(LOG_TAG, "will adopt " + permission);
198             } else { // We can't deny a permission to ourselves
199                 Log.d(LOG_TAG, "Can not deny " + permission);
200                 removePermissionContextsUntilCanApply();
201                 throw new NeneException("PermissionContext requires denying "
202                         + permission + " but cannot.");
203             }
204         }
205 
206         if (!adoptedShellPermissions.isEmpty()) {
207             Log.d(LOG_TAG, "Adopting " + adoptedShellPermissions);
208             ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(
209                     adoptedShellPermissions.toArray(new String[0]));
210         }
211     }
212 
clearPermissions()213     void clearPermissions() {
214         mPermissionContexts.clear();
215         applyPermissions();
216     }
217 
removePermissionContextsUntilCanApply()218     private void removePermissionContextsUntilCanApply() {
219         try {
220             mPermissionContexts.remove(mPermissionContexts.size() - 1);
221             applyPermissions();
222         } catch (NeneException e) {
223             // Suppress NeneException here as we may get a few as we pop through the stack
224         }
225     }
226 
canGrantPermission(String permission)227     private boolean canGrantPermission(String permission) {
228         try {
229             PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0);
230             if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) {
231                 return true;
232             }
233             if ((p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0) {
234                 return true;
235             }
236 
237             return false;
238         } catch (PackageManager.NameNotFoundException e) {
239             return false;
240         }
241     }
242 
recordExistingPermissions()243     private void recordExistingPermissions() {
244         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
245             return;
246         }
247 
248         mExistingPermissions = ShellCommandUtils.uiAutomation().getAdoptedShellPermissions();
249     }
250 
restoreExistingPermissions()251     private void restoreExistingPermissions() {
252         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
253             return;
254         }
255 
256         if (mExistingPermissions.isEmpty()) {
257             ShellCommandUtils.uiAutomation().dropShellPermissionIdentity();
258         } else if (mExistingPermissions == UiAutomation.ALL_PERMISSIONS) {
259             ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity();
260         } else {
261             ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(
262                     mExistingPermissions.toArray(new String[0]));
263         }
264 
265         mExistingPermissions = null;
266     }
267 }
268