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.permissions; 18 19 import static android.content.pm.PackageManager.PERMISSION_DENIED; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 import static android.cts.testapisreflection.TestApisReflectionKt.addOverridePermissionState; 22 import static android.cts.testapisreflection.TestApisReflectionKt.clearOverridePermissionStates; 23 import static android.cts.testapisreflection.TestApisReflectionKt.removeOverridePermissionState; 24 25 import android.app.AppOpsManager; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PermissionInfo; 29 import android.cts.testapisreflection.TestApisReflectionKt; 30 import android.os.Build; 31 import android.util.Log; 32 33 import com.android.bedstead.nene.TestApis; 34 import com.android.bedstead.nene.annotations.Experimental; 35 import com.android.bedstead.nene.appops.AppOpsMode; 36 import com.android.bedstead.nene.exceptions.NeneException; 37 import com.android.bedstead.nene.packages.Package; 38 import com.android.bedstead.nene.users.UserReference; 39 import com.android.bedstead.nene.utils.FailureDumper; 40 import com.android.bedstead.nene.utils.ShellCommand; 41 import com.android.bedstead.nene.utils.ShellCommandUtils; 42 import com.android.bedstead.nene.utils.Tags; 43 import com.android.bedstead.nene.utils.UndoableContext; 44 import com.android.bedstead.nene.utils.Versions; 45 46 import com.google.common.collect.ImmutableSet; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Collection; 51 import java.util.Collections; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Set; 55 import java.util.concurrent.atomic.AtomicBoolean; 56 import java.util.stream.Collectors; 57 58 /** Permission manager for tests. */ 59 public final class Permissions { 60 61 public static final AtomicBoolean sIgnorePermissions = new AtomicBoolean(false); 62 private static final String LOG_TAG = "Permissions"; 63 private static final Context sContext = TestApis.context().instrumentedContext(); 64 private static final PackageManager sPackageManager = sContext.getPackageManager(); 65 private static final AppOpsManager sAppOpsManager = 66 TestApis.context().instrumentedContext().getSystemService(AppOpsManager.class); 67 private static final Package sInstrumentedPackage = 68 TestApis.packages().instrumented(); 69 private static final UserReference sUser = TestApis.users().instrumented(); 70 private static final Package sShellPackage = 71 TestApis.packages().find("com.android.shell"); 72 private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS = 73 Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; 74 75 /** 76 * Permissions which cannot be given to shell. 77 * 78 * <p>Each entry must include a comment with the reason it cannot be added. 79 */ 80 private static final ImmutableSet EXEMPT_SHELL_PERMISSIONS = ImmutableSet.of( 81 82 ); 83 84 public static final Permissions sInstance = new Permissions(); 85 86 private final List<PermissionContextImpl> mPermissionContexts = 87 Collections.synchronizedList(new ArrayList<>()); 88 private final Set<String> mShellPermissions; 89 private final Set<String> mInstrumentedRequestedPermissions; 90 ignoringPermissions()91 public static UndoableContext ignoringPermissions() { 92 boolean original = Permissions.sIgnorePermissions.get(); 93 Permissions.sIgnorePermissions.set(true); 94 95 if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) { 96 adoptShellPermissionIdentity(); 97 } 98 99 return new UndoableContext(() -> { 100 if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) { 101 dropShellPermissionIdentity(); 102 } 103 Permissions.sIgnorePermissions.set(original); 104 }); 105 } 106 107 private Permissions() { 108 // Packages requires using INTERACT_ACROSS_USERS_FULL but we don't want it to rely on 109 // Permissions or it'll recurse forever - so we disable permission checks and just use 110 // shell permission adoption directly while initialising 111 try (UndoableContext c = ignoringPermissions()) { 112 if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) { 113 mShellPermissions = sShellPackage.requestedPermissions(); 114 } else { 115 mShellPermissions = new HashSet<>(); 116 } 117 mInstrumentedRequestedPermissions = sInstrumentedPackage.requestedPermissions(); 118 } 119 } 120 121 /** 122 * Enter a {@link PermissionContext} where the given permissions are granted. 123 * 124 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 125 * thrown. 126 * 127 * <p>Recommended usage: 128 * {@code 129 * 130 * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) { 131 * // Code which needs the permissions goes here 132 * } 133 * } 134 */ 135 public PermissionContextImpl withPermission(String... permissions) { 136 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 137 mPermissionContexts.add(permissionContext); 138 139 PermissionContextImpl unused = permissionContext.withPermission(permissions); 140 141 return permissionContext; 142 } 143 144 /** 145 * Enter a {@link PermissionContext} where the given permissions are granted only when running 146 * on the given version or above. 147 * 148 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 149 * thrown. 150 * 151 * <p>If the version does not match, the permission context will not change. 152 */ 153 public PermissionContextImpl withPermissionOnVersionAtLeast( 154 int minSdkVersion, String... permissions) { 155 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 156 mPermissionContexts.add(permissionContext); 157 158 PermissionContextImpl unused = 159 permissionContext.withPermissionOnVersionAtLeast(minSdkVersion, permissions); 160 161 return permissionContext; 162 } 163 164 /** 165 * Enter a {@link PermissionContext} where the given permissions are granted only when running 166 * on the given version or below. 167 * 168 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 169 * thrown. 170 * 171 * <p>If the version does not match, the permission context will not change. 172 */ 173 public PermissionContextImpl withPermissionOnVersionAtMost( 174 int maxSdkVersion, String... permissions) { 175 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 176 mPermissionContexts.add(permissionContext); 177 178 PermissionContextImpl unused = 179 permissionContext.withPermissionOnVersionAtMost(maxSdkVersion, permissions); 180 181 return permissionContext; 182 } 183 184 /** 185 * Enter a {@link PermissionContext} where the given permissions are granted only when running 186 * on the range of versions given (inclusive). 187 * 188 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 189 * thrown. 190 * 191 * <p>If the version does not match, the permission context will not change. 192 */ 193 public PermissionContextImpl withPermissionOnVersionBetween( 194 int minSdkVersion, int maxSdkVersion, String... permissions) { 195 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 196 mPermissionContexts.add(permissionContext); 197 198 PermissionContextImpl unused = 199 permissionContext.withPermissionOnVersionBetween(minSdkVersion, maxSdkVersion, 200 permissions); 201 202 return permissionContext; 203 } 204 205 /** 206 * Enter a {@link PermissionContext} where the given permissions are granted only when running 207 * on the given version. 208 * 209 * <p>If the permissions cannot be granted, and are not already granted, an exception will be 210 * thrown. 211 * 212 * <p>If the version does not match, the permission context will not change. 213 */ 214 public PermissionContextImpl withPermissionOnVersion(int sdkVersion, String... permissions) { 215 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 216 mPermissionContexts.add(permissionContext); 217 218 PermissionContextImpl unused = 219 permissionContext.withPermissionOnVersion(sdkVersion, permissions); 220 221 return permissionContext; 222 } 223 224 /** 225 * Enter a {@link PermissionContext} where the given appOps are granted. 226 * 227 * <p>If the appOps cannot be granted, and are not already granted, an exception will be 228 * thrown. 229 * 230 * <p>Recommended usage: 231 * {@code 232 * 233 * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) { 234 * // Code which needs the app ops goes here 235 * } 236 * } 237 */ 238 public PermissionContextImpl withAppOp(String... appOps) { 239 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 240 mPermissionContexts.add(permissionContext); 241 242 PermissionContextImpl unused = permissionContext.withAppOp(appOps); 243 244 return permissionContext; 245 } 246 247 /** 248 * Enter a {@link PermissionContext} where the given appOps are granted. 249 * 250 * <p>If the appOps cannot be granted, and are not already granted, an exception will be 251 * thrown. 252 * 253 * <p>Recommended usage: 254 * {@code 255 * 256 * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) { 257 * // Code which needs the app ops goes here 258 * } 259 * } 260 * 261 * <p>If the version does not match the appOp will not be granted. 262 */ 263 public PermissionContextImpl withAppOpOnVersion(int sdkVersion, String... appOps) { 264 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 265 mPermissionContexts.add(permissionContext); 266 267 PermissionContextImpl unused = permissionContext.withAppOpOnVersion(sdkVersion, appOps); 268 269 return permissionContext; 270 } 271 272 /** 273 * Enter a {@link PermissionContext} where the given appOps are granted. 274 * 275 * <p>If the appOps cannot be granted, and are not already granted, an exception will be 276 * thrown. 277 * 278 * <p>Recommended usage: 279 * {@code 280 * 281 * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) { 282 * // Code which needs the app ops goes here 283 * } 284 * } 285 * 286 * <p>If the version does not match the appOp will not be granted. 287 */ 288 public PermissionContextImpl withAppOpOnVersionAtLeast(int sdkVersion, String... appOps) { 289 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 290 mPermissionContexts.add(permissionContext); 291 292 PermissionContextImpl unused = permissionContext.withAppOpOnVersionAtLeast(sdkVersion, 293 appOps); 294 295 return permissionContext; 296 } 297 298 /** 299 * Enter a {@link PermissionContext} where the given appOps are granted. 300 * 301 * <p>If the appOps cannot be granted, and are not already granted, an exception will be 302 * thrown. 303 * 304 * <p>Recommended usage: 305 * {@code 306 * 307 * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) { 308 * // Code which needs the app ops goes here 309 * } 310 * } 311 * 312 * <p>If the version does not match the appOp will not be granted. 313 */ 314 public PermissionContextImpl withAppOpOnVersionAtMost(int sdkVersion, String... appOps) { 315 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 316 mPermissionContexts.add(permissionContext); 317 318 PermissionContextImpl unused = permissionContext.withAppOpOnVersionAtMost(sdkVersion, 319 appOps); 320 321 return permissionContext; 322 } 323 324 /** 325 * Enter a {@link PermissionContext} where the given appOps are granted. 326 * 327 * <p>If the appOps cannot be granted, and are not already granted, an exception will be 328 * thrown. 329 * 330 * <p>Recommended usage: 331 * {@code 332 * 333 * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) { 334 * // Code which needs the app ops goes here 335 * } 336 * } 337 * 338 * <p>If the version does not match the appOp will not be granted. 339 */ 340 public PermissionContextImpl withAppOpOnVersionBetween( 341 int minSdkVersion, int maxSdkVersion, String... appOps) { 342 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 343 mPermissionContexts.add(permissionContext); 344 345 PermissionContextImpl unused = permissionContext.withAppOpOnVersionBetween(minSdkVersion, 346 maxSdkVersion, appOps); 347 348 return permissionContext; 349 } 350 351 /** 352 * Enter a {@link PermissionContext} where the given permissions are not granted. 353 * 354 * <p>If the permissions cannot be denied, and are not already denied, an exception will be 355 * thrown. 356 * 357 * <p>Recommended usage: 358 * {@code 359 * 360 * try (PermissionContext p = 361 * mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) { 362 * // Code which needs the permissions to be denied goes here 363 * } 364 */ 365 public PermissionContextImpl withoutPermission(String... permissions) { 366 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 367 mPermissionContexts.add(permissionContext); 368 369 PermissionContextImpl unused = permissionContext.withoutPermission(permissions); 370 371 return permissionContext; 372 } 373 374 /** 375 * Enter a {@link PermissionContext} where the given appOps are not granted. 376 * 377 * <p>If the appOps cannot be denied, and are not already denied, an exception will be 378 * thrown. 379 * 380 * <p>Recommended usage: 381 * {@code 382 * 383 * try (PermissionContext p = 384 * mTestApis.permissions().withoutappOp(APP_OP1, APP_OP2) { 385 * // Code which needs the appOp to be denied goes here 386 * } 387 * } 388 */ 389 public PermissionContextImpl withoutAppOp(String... appOps) { 390 PermissionContextImpl permissionContext = PermissionContextImpl.create(this); 391 mPermissionContexts.add(permissionContext); 392 393 PermissionContextImpl unused = permissionContext.withoutAppOp(appOps); 394 395 return permissionContext; 396 } 397 398 void undoPermission(PermissionContextImpl permissionContext) { 399 boolean unused = mPermissionContexts.remove(permissionContext); 400 applyPermissions(/* removedPermissionContext = */ permissionContext); 401 } 402 403 void applyPermissions() { 404 applyPermissions(null); 405 } 406 407 private void applyPermissions(PermissionContextImpl removedPermissionContext) { 408 if (sIgnorePermissions.get()) { 409 return; 410 } 411 412 Set<String> grantedPermissions = new HashSet<>(); 413 Set<String> deniedPermissions = new HashSet<>(); 414 Set<String> grantedAppOps = new HashSet<>(); 415 Set<String> deniedAppOps = new HashSet<>(); 416 417 synchronized (mPermissionContexts) { 418 for (PermissionContextImpl permissionContext : mPermissionContexts) { 419 for (String permission : permissionContext.grantedPermissions()) { 420 grantedPermissions.add(permission); 421 deniedPermissions.remove(permission); 422 } 423 424 for (String permission : permissionContext.deniedPermissions()) { 425 grantedPermissions.remove(permission); 426 deniedPermissions.add(permission); 427 } 428 429 for (String appOp : permissionContext.grantedAppOps()) { 430 grantedAppOps.add(appOp); 431 deniedAppOps.remove(appOp); 432 } 433 434 for (String appOp : permissionContext.deniedAppOps()) { 435 grantedAppOps.remove(appOp); 436 deniedAppOps.add(appOp); 437 } 438 } 439 } 440 441 setPermissionState( 442 TestApis.packages().instrumented(), 443 TestApis.users().instrumented(), 444 grantedPermissions, 445 deniedPermissions); 446 Package appOpPackage = 447 hasAdoptedShellPermissionIdentity ? sShellPackage : sInstrumentedPackage; 448 setAppOpState( 449 appOpPackage, 450 TestApis.users().instrumented(), 451 grantedAppOps, 452 deniedAppOps 453 ); 454 455 if (removedPermissionContext != null) { 456 removedPermissionContext.grantedAppOps().stream().filter( 457 (i) -> !grantedAppOps.contains(i) && !deniedAppOps.contains(i)) 458 .forEach(i -> appOpPackage.appOps().set(i, AppOpsMode.DEFAULT)); 459 removedPermissionContext.deniedAppOps().stream().filter( 460 (i) -> !grantedAppOps.contains(i) && !deniedAppOps.contains(i)) 461 .forEach(i -> appOpPackage.appOps().set(i, AppOpsMode.DEFAULT)); 462 } 463 464 } 465 466 /** 467 * Throw an exception including permission contextual information. 468 */ 469 public void throwPermissionException( 470 String message, String permission) { 471 String protectionLevel = "Permission not found"; 472 try { 473 protectionLevel = Integer.toString(sPackageManager.getPermissionInfo( 474 permission, /* flags= */ 0).protectionLevel); 475 } catch (PackageManager.NameNotFoundException e) { 476 Log.e(LOG_TAG, "Permission not found", e); 477 } 478 479 480 481 try (UndoableContext c = ignoringPermissions()){ 482 throw new NeneException( 483 message 484 + "\n\n" 485 + "If this is a new test. Consider moving it to a root-enabled test" 486 + " suite and adding @RequireRootInstrumentation to the method. This" 487 + " enables arbitrary use of permissions.\n\n" 488 + "Running On User: " 489 + sUser 490 + "\nPermission: " 491 + permission 492 + "\nPermission protection level: " 493 + protectionLevel 494 + "\nPermission state: " 495 + sContext.checkSelfPermission(permission) 496 + "\nInstrumented Package: " 497 + sInstrumentedPackage.packageName() 498 + "\n\nRequested Permissions:\n" 499 + sInstrumentedPackage.requestedPermissions() 500 + "\n\nCan adopt shell permissions: " 501 + SUPPORTS_ADOPT_SHELL_PERMISSIONS 502 + "\nShell permissions:" 503 + mShellPermissions 504 + "\nExempt Shell permissions: " 505 + EXEMPT_SHELL_PERMISSIONS); 506 } 507 } 508 509 void clearPermissions() { 510 mPermissionContexts.clear(); 511 applyPermissions(); 512 } 513 514 /** 515 * Returns all of the permissions which can be adopted. 516 */ 517 public Set<String> adoptablePermissions() { 518 return mShellPermissions; 519 } 520 521 /** 522 * Returns all of the permissions which are currently able to be used. 523 */ 524 public Set<String> usablePermissions() { 525 Set<String> usablePermissions = new HashSet<>(); 526 usablePermissions.addAll(mShellPermissions); 527 usablePermissions.addAll(mInstrumentedRequestedPermissions); 528 return usablePermissions; 529 } 530 531 private void removePermissionContextsUntilCanApplyPermissions() { 532 boolean appliedPermissions = false; 533 while (!mPermissionContexts.isEmpty() && !appliedPermissions) { 534 try { 535 mPermissionContexts.remove(mPermissionContexts.size() - 1); 536 applyPermissions(); 537 appliedPermissions = true; 538 } catch (NeneException e) { 539 // Suppress NeneException here as we may get a few as we pop through the stack 540 } 541 } 542 } 543 544 private boolean canGrantPermission(String permission) { 545 try { 546 PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0); 547 if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) { 548 return true; 549 } 550 return (p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0; 551 } catch (PackageManager.NameNotFoundException e) { 552 return false; 553 } 554 } 555 556 /** True if the current process has the given permission. */ 557 public boolean hasPermission(String permission) { 558 return sContext.checkSelfPermission(permission) == PERMISSION_GRANTED; 559 } 560 561 /** 562 * True if the current process has the given appOp set to ALLOWED. 563 * 564 * <p>This accounts for shell identity being adopted (in which case it will check the appop 565 * status of the shell identity). 566 */ 567 public boolean hasAppOpAllowed(String appOp) { 568 Package appOpPackage = sInstrumentedPackage; 569 if (hasAdoptedShellPermissionIdentity) { 570 // We care about the shell package 571 appOpPackage = sShellPackage; 572 } 573 574 return appOpPackage.appOps().get(appOp) == AppOpsMode.ALLOWED; 575 } 576 577 /** 578 * Sets a permission state for a given package on a given user. 579 * 580 * <p>Generally tests should not use this method directly. They should instead used the {@link 581 * #withPermission} and {@link #withoutPermission} methods. 582 * 583 * <p>When this is used while executing a test which uses the RequireRootInstrumentation 584 * annotation, and using Android 15+, it will have access to all permissions for all apps. 585 * 586 * <p>Otherwise, when applying to the instrumented package, shell permission adoption will be 587 * used. 588 * 589 * <p>Otherwise, if the permission is able to be granted/denied by ADB then that will be done. 590 * 591 * <p>Otherwise an error will be thrown. 592 */ 593 public void setPermissionState( 594 Package pkg, 595 UserReference user, 596 Collection<String> permissionsToGrant, 597 Collection<String> permissionsToDeny) { 598 FailureDumper.Companion.getFailureDumpers().add( 599 "com.android.bedstead.permissions.PermissionsAnnotationExecutor"); 600 // TODO: replace with dependency on bedstead-root when properly modularised 601 if (Tags.hasTag("root-instrumentation") 602 && Versions.meetsMinimumSdkVersionRequirement(Versions.V)) { 603 // We must reset as it may have been set previously 604 resetRootPermissionState(pkg, user); 605 606 for (String grantedPermission : permissionsToGrant) { 607 forceRootPermissionState(pkg, user, grantedPermission, true); 608 } 609 for (String deniedPermission : permissionsToDeny) { 610 forceRootPermissionState(pkg, user, deniedPermission, false); 611 } 612 613 return; 614 } 615 616 setPermissionStateToPackageWithoutRoot(pkg, user, permissionsToGrant, permissionsToDeny); 617 } 618 619 /** 620 * Sets an appOp state for a given package on a given user. 621 * 622 * Generally tests should not use this method directly. They should instead used the 623 * {@link #withAppOp} and {@link #withoutAppOp} methods. 624 * 625 * Note that if shell permission identity is adopted, then the app op state will not be queried 626 * for the package - and the shell package should have its app op state set instead. 627 */ 628 public void setAppOpState(Package pkg, UserReference user, Collection<String> grantedAppOps, 629 Collection<String> deniedAppOps) { 630 FailureDumper.Companion.getFailureDumpers().add( 631 "com.android.bedstead.permissions.PermissionsAnnotationExecutor"); 632 // Filter so we get just the appOps which require a state that they are not currently in 633 Set<String> filteredGrantedAppOps = grantedAppOps.stream() 634 .filter(o -> pkg.appOps().get(o) != AppOpsMode.ALLOWED) 635 .collect(Collectors.toSet()); 636 Set<String> filteredDeniedAppOps = deniedAppOps.stream() 637 .filter(o -> pkg.appOps().get(o) != AppOpsMode.IGNORED) 638 .collect(Collectors.toSet()); 639 640 if (!filteredGrantedAppOps.isEmpty() || !filteredDeniedAppOps.isEmpty()) { 641 // We need MANAGE_APP_OPS_MODES to change app op permissions - but don't want to 642 // infinite loop so won't use .appOps().set() 643 Set<String> previousAdoptedShellPermissions = getAdoptedShellPermissions(); 644 adoptShellPermissionIdentity(CommonPermissions.MANAGE_APP_OPS_MODES); 645 for (String appOp : filteredGrantedAppOps) { 646 sAppOpsManager.setMode(appOp, pkg.uid(sUser), 647 pkg.packageName(), AppOpsMode.ALLOWED.value()); 648 } 649 for (String appOp : filteredDeniedAppOps) { 650 sAppOpsManager.setMode(appOp, pkg.uid(sUser), 651 pkg.packageName(), AppOpsMode.IGNORED.value()); 652 } 653 654 adoptShellPermissionIdentity(previousAdoptedShellPermissions); 655 } 656 } 657 658 private void setPermissionStateToPackageWithoutAdoption(Package pkg, UserReference user, 659 Collection<String> permissionsToGrant, Collection<String> permissionsToDeny) { 660 for (String permission : permissionsToGrant) { 661 if (canGrantPermission(permission)) { 662 pkg.grantPermission(user, permission); 663 } else { 664 removePermissionContextsUntilCanApplyPermissions(); 665 throwPermissionException( 666 "Requires granting permission " + permission + " but cannot.", permission); 667 } 668 } 669 670 for (String permission : permissionsToDeny) { 671 if (pkg.equals(TestApis.packages().instrumented()) && user.equals( 672 TestApis.users().instrumented())) { 673 // We can't deny permissions from ourselves or it'll kill the process 674 removePermissionContextsUntilCanApplyPermissions(); 675 throwPermissionException( 676 "Requires denying permission " + permission + " but cannot.", permission); 677 } else { 678 pkg.denyPermission(user, permission); 679 } 680 } 681 } 682 683 private void setPermissionStateToPackageWithoutRoot(Package pkg, UserReference user, 684 Collection<String> permissionsToGrant, Collection<String> permissionsToDeny) { 685 if (!pkg.equals(TestApis.packages().instrumented()) || !SUPPORTS_ADOPT_SHELL_PERMISSIONS) { 686 // We can't adopt... 687 setPermissionStateToPackageWithoutAdoption(pkg, user, permissionsToGrant, 688 permissionsToDeny); 689 return; 690 } 691 692 if (TestApis.packages().instrumented().isInstantApp()) { 693 // Instant Apps aren't able to know the permissions of shell so we can't know if we can 694 // adopt it - we'll assume we can adopt and log 695 Log.i(LOG_TAG, 696 "Adopting all shell permissions as can't check shell: " + mPermissionContexts); 697 adoptShellPermissionIdentity(); 698 return; 699 } 700 701 dropShellPermissionIdentity(); 702 // We first try to use shell permissions, because they can be revoked/etc. much more easily 703 704 Set<String> adoptedShellPermissions = new HashSet<>(); 705 Set<String> grantedPermissions = new HashSet<>(); 706 Set<String> deniedPermissions = new HashSet<>(); 707 for (String permission : permissionsToGrant) { 708 Log.d(LOG_TAG, "Trying to grant " + permission); 709 if (sInstrumentedPackage.hasPermission(user, permission)) { 710 // Already granted, can skip 711 Log.d(LOG_TAG, permission + " already granted at runtime"); 712 } else if (mInstrumentedRequestedPermissions.contains(permission) 713 && sContext.checkSelfPermission(permission) == PERMISSION_GRANTED) { 714 // Already granted, can skip 715 Log.d(LOG_TAG, permission + " already granted from manifest"); 716 } else if (mShellPermissions.contains(permission)) { 717 adoptedShellPermissions.add(permission); 718 } else { 719 grantedPermissions.add(permission); 720 } 721 } 722 723 for (String permission : permissionsToDeny) { 724 if (!sInstrumentedPackage.hasPermission(sUser, permission)) { 725 // Already denied, can skip 726 } else if (!mShellPermissions.contains(permission)) { 727 adoptedShellPermissions.add(permission); 728 } else { 729 deniedPermissions.add(permission); 730 } 731 } 732 733 if (!adoptedShellPermissions.isEmpty()) { 734 adoptShellPermissionIdentity(adoptedShellPermissions); 735 } 736 if (!grantedPermissions.isEmpty() || !deniedPermissions.isEmpty()) { 737 setPermissionStateToPackageWithoutAdoption(pkg, user, grantedPermissions, 738 deniedPermissions); 739 } 740 } 741 742 /** Ensure that permissions are not being overridden for any packages. */ 743 @Experimental 744 public void clearAllOverridePermissionStates() { 745 if (Versions.meetsMinimumSdkVersionRequirement(Versions.V)) { 746 try { 747 TestApisReflectionKt.clearAllOverridePermissionStates( 748 ShellCommandUtils.uiAutomation()); 749 } catch (Exception e) { 750 Log.e(LOG_TAG, "Error clearing all override permission states", e); 751 } 752 } 753 } 754 755 private void resetRootPermissionState(Package pkg, UserReference user) { 756 clearOverridePermissionStates(ShellCommandUtils.uiAutomation(), pkg.uid(user)); 757 } 758 759 private void forceRootPermissionState(Package pkg, UserReference user, String permission, 760 boolean granted) { 761 addOverridePermissionState( 762 ShellCommandUtils.uiAutomation(), pkg.uid(user), permission, 763 granted ? PERMISSION_GRANTED : PERMISSION_DENIED); 764 } 765 766 public void removeRootPermissionState(Package pkg, UserReference user, String permission) { 767 removeOverridePermissionState( 768 ShellCommandUtils.uiAutomation(), pkg.uid(user), permission); 769 } 770 771 private static boolean hasAdoptedShellPermissionIdentity = false; 772 773 private static void adoptShellPermissionIdentity(String... permissions) { 774 adoptShellPermissionIdentity(new HashSet<>(Arrays.asList(permissions))); 775 } 776 777 private static void adoptShellPermissionIdentity(Collection<String> permissions) { 778 if (permissions.isEmpty()) { 779 dropShellPermissionIdentity(); 780 return; 781 } 782 783 Log.d(LOG_TAG, "Adopting " + permissions); 784 hasAdoptedShellPermissionIdentity = true; 785 ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity( 786 permissions.toArray(new String[0])); 787 788 if (Versions.meetsMinimumSdkVersionRequirement(Versions.S)) { 789 // b/365494315: verify if the adoption was successful - for S+ only as 790 // UiAutomation#getAdoptedShellPermissions was added in S. 791 Set<String> adoptedPermissions = getAdoptedShellPermissions(); 792 if (!adoptedPermissions.containsAll(permissions)) { 793 String message = "Expected all of the following permissions to be adopted but " 794 + "were not: " + permissions + ". Actual adopted permissions: " + 795 adoptedPermissions + ". See the stacktrace to find the caller."; 796 throw new NeneException(message); 797 } 798 } 799 } 800 801 private static void adoptShellPermissionIdentity() { 802 Log.d(LOG_TAG, "Adopting all shell permissions"); 803 hasAdoptedShellPermissionIdentity = true; 804 ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(); 805 } 806 807 private static void dropShellPermissionIdentity() { 808 Log.d(LOG_TAG, "Dropping shell permissions"); 809 hasAdoptedShellPermissionIdentity = false; 810 ShellCommandUtils.uiAutomation().dropShellPermissionIdentity(); 811 } 812 813 /** Get string dump of permissions state. */ 814 public String dump() { 815 if (!Versions.meetsMinimumSdkVersionRequirement(Versions.V)) { 816 Log.i(LOG_TAG, "Cannot dump permission before V so dumping packages"); 817 return TestApis.packages().dump(); 818 } 819 820 return ShellCommand.builder("dumpsys permissionmgr").validate((s) -> !s.isEmpty()) 821 .executeOrThrowNeneException("Error dumping permission state"); 822 } 823 824 /** 825 * Get adopted shell permissions. 826 * 827 * <p>See {@code UiAutomation#getAdoptedShellPermissions}. 828 * 829 * <p>Note: Does not compute anything for Versions < S, returns an empty set instead. 830 */ 831 private static Set<String> getAdoptedShellPermissions() { 832 if (Versions.meetsMinimumSdkVersionRequirement(Versions.S)) { 833 return TestApisReflectionKt.getAdoptedShellPermissions( 834 ShellCommandUtils.uiAutomation()); 835 } 836 837 return Collections.emptySet(); 838 } 839 840 /** 841 * create a PermissionContextImpl that doesn't block others threads to create other contexts 842 * don't use it in the tests directly 843 */ 844 public PermissionContextImpl createNonBlockingPermissionContext() { 845 var context = PermissionContextImpl.createNonBlocking(this); 846 mPermissionContexts.add(context); 847 return context; 848 } 849 } 850 851