From 003453b35460f30a114a210eb43a3970b6b0905a Mon Sep 17 00:00:00 2001 From: Eran Messeri Date: Mon, 1 Feb 2021 14:40:55 +0000 Subject: [PATCH] Restrict Admin grant of sensors-related permissions Restrict the admin of a fully-managed device or managed profile from granting sensors-related permissions. The admin of a managed profile cannot control permission grants for sensors-related permissions at all. The admin of a fully-managed device can opt-out of having said control by providing a provisioning extra. This change passes the boolean flag in ActiveAdmin indicating whether the admin has control over sensor permission grants into the permission controller. Manual testing: * Install TestDPC * Create a work profile using TestDPC. * Get the BasicLocation app by checking out https://github.com/android/location-samples and building it from there. * Install the app onto the device but do not start it. * In TestDPC, Find "Manage app permissions", choose "Basic Location Sample" from the drop-down menu. * Toggle each of the "ACCESS_COARSE_LOCATION" and "ACCESS_BACKGROUND_LOCATION" to "Allow". * Observe that no notification appears. * Start the BasicLocation app and observe the runtime permission prompt shows up. Bug: 158735247 Test: Manual (more to be added). Test: cts (see topic) Change-Id: I12d9f7e24ad4bc09651a5e5f60b864298506c2c4 --- core/api/system-current.txt | 13 +- .../AdminPermissionControlParams.aidl | 19 +++ .../AdminPermissionControlParams.java | 132 ++++++++++++++++++ .../permission/IPermissionController.aidl | 5 +- .../PermissionControllerManager.java | 35 ++--- .../PermissionControllerService.java | 37 +++-- .../DevicePolicyManagerService.java | 14 +- 7 files changed, 216 insertions(+), 39 deletions(-) create mode 100644 core/java/android/permission/AdminPermissionControlParams.aidl create mode 100644 core/java/android/permission/AdminPermissionControlParams.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f5a22543f9405..03ce15f161713 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -8910,6 +8910,16 @@ package android.os.storage { package android.permission { + public final class AdminPermissionControlParams implements android.os.Parcelable { + method public boolean canAdminGrantSensorsPermissions(); + method public int describeContents(); + method public int getGrantState(); + method @NonNull public String getGranteePackageName(); + method @NonNull public String getPermission(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); @@ -8941,7 +8951,8 @@ package android.permission { method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer>>); - method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer); + method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer); + method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable); method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable); diff --git a/core/java/android/permission/AdminPermissionControlParams.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl new file mode 100644 index 0000000000000..35e63d4851fcd --- /dev/null +++ b/core/java/android/permission/AdminPermissionControlParams.aidl @@ -0,0 +1,19 @@ +/** + * 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 android.permission; + +parcelable AdminPermissionControlParams; diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java new file mode 100644 index 0000000000000..49507220e7b06 --- /dev/null +++ b/core/java/android/permission/AdminPermissionControlParams.java @@ -0,0 +1,132 @@ +/* + * 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 android.permission; + +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.admin.DevicePolicyManager; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A data object representing an admin's request to control a certain permission + * for a certain app. + * This class is processed by the Permission Controller's + * setRuntimePermissionGrantStateByDeviceAdmin method. + * + * @hide + */ +@SystemApi +public final class AdminPermissionControlParams implements Parcelable { + // The package to grant/deny the permission to. + private final @NonNull String mGranteePackageName; + // The permission to grant/deny. + private final @NonNull String mPermission; + // The grant state (granted/denied/default). + private final @DevicePolicyManager.PermissionGrantState int mGrantState; + // Whether the admin can grant sensors-related permissions. + private final boolean mCanAdminGrantSensorsPermissions; + + /** + * @hide + * A new instance is only created by the framework, so the constructor need not be visible + * as system API. + */ + public AdminPermissionControlParams(@NonNull String granteePackageName, + @NonNull String permission, + int grantState, boolean canAdminGrantSensorsPermissions) { + Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty."); + Preconditions.checkStringNotEmpty(permission, "Permission must not be empty."); + checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED + || grantState == PERMISSION_GRANT_STATE_DENIED + || grantState == PERMISSION_GRANT_STATE_DEFAULT); + + mGranteePackageName = granteePackageName; + mPermission = permission; + mGrantState = grantState; + mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions; + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public AdminPermissionControlParams createFromParcel(Parcel in) { + String granteePackageName = in.readString(); + String permission = in.readString(); + int grantState = in.readInt(); + boolean mayAdminGrantSensorPermissions = in.readBoolean(); + + return new AdminPermissionControlParams(granteePackageName, permission, + grantState, mayAdminGrantSensorPermissions); + } + + @Override + public AdminPermissionControlParams[] newArray(int size) { + return new AdminPermissionControlParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mGranteePackageName); + dest.writeString(mPermission); + dest.writeInt(mGrantState); + dest.writeBoolean(mCanAdminGrantSensorsPermissions); + } + + /** Returns the name of the package the permission applies to */ + public @NonNull String getGranteePackageName() { + return mGranteePackageName; + } + + /** Returns the permission name */ + public @NonNull String getPermission() { + return mPermission; + } + + /** Returns the grant state */ + public int getGrantState() { + return mGrantState; + } + + /** + * return true if the admin may control grants of permissions related to sensors. + */ + public boolean canAdminGrantSensorsPermissions() { + return mCanAdminGrantSensorsPermissions; + } + + @Override + public String toString() { + return String.format( + "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b", + mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions); + } +} diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 084cc2ff4aa41..6d677f35b563a 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -20,6 +20,7 @@ import android.os.RemoteCallback; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.UserHandle; +import android.permission.AdminPermissionControlParams; import com.android.internal.infra.AndroidFuture; /** @@ -39,8 +40,8 @@ oneway interface IPermissionController { void countPermissionApps(in List permissionNames, int flags, in AndroidFuture callback); void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback); - void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName, - String permission, int grantState, in AndroidFuture callback); + void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName, + in AdminPermissionControlParams params, in AndroidFuture callback); void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback); void notifyOneTimePermissionSessionTimeout(String packageName); void updateUserSensitiveForApp(int uid, in AndroidFuture callback); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index f306805ac3a4b..084b18eb29991 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -16,13 +16,9 @@ package android.permission; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerService.SERVICE_INTERFACE; import static com.android.internal.util.FunctionalUtils.uncheckExceptions; -import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkFlagsArgument; @@ -39,7 +35,6 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityThread; -import android.app.admin.DevicePolicyManager.PermissionGrantState; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -70,6 +65,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -323,11 +319,11 @@ public final class PermissionControllerManager { /** * Set the runtime permission state from a device admin. + * This variant takes into account whether the admin may or may not grant sensors-related + * permissions. * * @param callerPackageName The package name of the admin requesting the change - * @param packageName Package the permission belongs to - * @param permission Permission to change - * @param grantState State to set the permission into + * @param params Information about the permission being granted. * @param executor Executor to run the {@code callback} on * @param callback The callback * @@ -338,30 +334,27 @@ public final class PermissionControllerManager { Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY}, conditional = true) public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, - @NonNull String packageName, @NonNull String permission, - @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor, + @NonNull AdminPermissionControlParams params, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { checkStringNotEmpty(callerPackageName); - checkStringNotEmpty(packageName); - checkStringNotEmpty(permission); - checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED - || grantState == PERMISSION_GRANT_STATE_DENIED - || grantState == PERMISSION_GRANT_STATE_DEFAULT); - checkNotNull(executor); - checkNotNull(callback); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Objects.requireNonNull(params, "Admin control params must not be null."); mRemoteService.postAsync(service -> { AndroidFuture setRuntimePermissionGrantStateResult = new AndroidFuture<>(); - service.setRuntimePermissionGrantStateByDeviceAdmin( - callerPackageName, packageName, permission, grantState, + service.setRuntimePermissionGrantStateByDeviceAdminFromParams( + callerPackageName, params, setRuntimePermissionGrantStateResult); return setRuntimePermissionGrantStateResult; }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> { final long token = Binder.clearCallingIdentity(); try { if (err != null) { - Log.e(TAG, "Error setting permissions state for device admin " + packageName, - err); + Log.e(TAG, + "Error setting permissions state for device admin " + + callerPackageName, err); callback.accept(false); } else { callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult)); diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 8105b65170158..ad9e8b3d6dd43 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -16,9 +16,7 @@ package android.permission; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED; import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM; @@ -259,6 +257,8 @@ public abstract class PermissionControllerService extends Service { } /** + * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String, + * AdminPermissionControlParams, Consumer)}. * Set the runtime permission state from a device admin. * * @param callerPackageName The package name of the admin requesting the change @@ -267,12 +267,27 @@ public abstract class PermissionControllerService extends Service { * @param grantState State to set the permission into * @param callback Callback waiting for whether the state could be set or not */ + @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin( @NonNull String callerPackageName, @NonNull String packageName, @NonNull String permission, @PermissionGrantState int grantState, @NonNull Consumer callback); + /** + * Set the runtime permission state from a device admin. + * + * @param callerPackageName The package name of the admin requesting the change + * @param params Parameters of admin request. + * @param callback Callback waiting for whether the state could be set or not + */ + @BinderThread + public void onSetRuntimePermissionGrantStateByDeviceAdmin( + @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params, + @NonNull Consumer callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** * Called when a package is considered inactive based on the criteria given by * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}. @@ -468,32 +483,26 @@ public abstract class PermissionControllerService extends Service { } @Override - public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, - String packageName, String permission, int grantState, + public void setRuntimePermissionGrantStateByDeviceAdminFromParams( + String callerPackageName, AdminPermissionControlParams params, AndroidFuture callback) { checkStringNotEmpty(callerPackageName); - checkStringNotEmpty(packageName); - checkStringNotEmpty(permission); - checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED - || grantState == PERMISSION_GRANT_STATE_DENIED - || grantState == PERMISSION_GRANT_STATE_DEFAULT); - checkNotNull(callback); - - if (grantState == PERMISSION_GRANT_STATE_DENIED) { + if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) { enforceSomePermissionsGrantedToCaller( Manifest.permission.GRANT_RUNTIME_PERMISSIONS); } - if (grantState == PERMISSION_GRANT_STATE_DENIED) { + if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); } enforceSomePermissionsGrantedToCaller( Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + checkNotNull(callback); onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName, - packageName, permission, grantState, callback::complete); + params, callback::complete); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 498ee38d442af..28d12fab1e196 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -246,6 +246,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.permission.AdminPermissionControlParams; import android.permission.IPermissionManager; import android.permission.PermissionControllerManager; import android.provider.CalendarContract; @@ -7883,6 +7884,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateDeviceOwnerLocked(); setDeviceOwnershipSystemPropertyLocked(); + //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this + // hard-coded default value setting. + if (isAdb(caller)) { + activeAdmin.mAdminCanGrantSensorsPermissions = true; + mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true); + saveSettingsLocked(userId); + } + mInjector.binderWithCleanCallingIdentity(() -> { // Restrict adding a managed profile when a device owner is set on the device. // That is to prevent the co-existence of a managed profile and a device owner @@ -12635,9 +12644,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { + AdminPermissionControlParams permissionParams = + new AdminPermissionControlParams(packageName, permission, grantState, + canAdminGrantSensorsPermissionsForUser(caller.getUserId())); mInjector.getPermissionControllerManager(caller.getUserHandle()) .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(), - packageName, permission, grantState, mContext.getMainExecutor(), + permissionParams, mContext.getMainExecutor(), (permissionWasSet) -> { if (isPostQAdmin && !permissionWasSet) { callback.sendResult(null);