diff options
| author | 2024-03-21 13:02:13 +0000 | |
|---|---|---|
| committer | 2024-04-03 12:53:48 +0100 | |
| commit | 6a152f70a08333fa7be2b68b27824270e9a3cda0 (patch) | |
| tree | ff06732c1b3b08a44652366b1c50e48fd1138e8e | |
| parent | c6d3da582a0d92f3c7a23c5a9ceb48ae6c988df9 (diff) | |
Enable device management role holder to set app restrictions
Give DMRH the ability to set app restrictions on any target applications
in the calling user, as well as the parent user of a COPE profile. App
restrictions will be automatically propagated to the private space if they
are set on the parent user of a COPE profile.
App restrictions set by the DMRH are handled by policy engine as coexisable.
Existing app restrictions set by DPC are kept untouched so they are
mostly indepenent from the ones set by the DMRH.
Bug: 304969881
Test: ApplicationRestrictionsTest
Change-Id: Iffb8a0b4a7bf14350f52676d5d7d9d75a1c96393
5 files changed, 148 insertions, 25 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 60dffbd0e421..2f63dbb1d8f4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -10285,6 +10285,16 @@ public class DevicePolicyManager { * get the list of app restrictions set by each admin via * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin}. * + * <p>Starting from Android Version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * the device policy management role holder can also set app restrictions on any applications + * in the calling user, as well as the parent user of an organization-owned managed profile via + * the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)}. App restrictions set by the device policy + * management role holder are not returned by + * {@link UserManager#getApplicationRestrictions(String)}. The target application should use + * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} to retrieve + * them, alongside any app restrictions the profile or device owner might have set. + * * <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread * * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or @@ -10300,11 +10310,14 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { - throwIfParentInstance("setApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("setApplicationRestrictions"); + } + if (mService != null) { try { mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName, - settings); + settings, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -11705,11 +11718,14 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { - throwIfParentInstance("getApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("getApplicationRestrictions"); + } + if (mService != null) { try { return mService.getApplicationRestrictions(admin, mContext.getPackageName(), - packageName); + packageName, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -13989,8 +14005,15 @@ public class DevicePolicyManager { public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); try { - if (!mService.isManagedProfile(admin)) { - throw new SecurityException("The current user does not have a parent profile."); + if (Flags.dmrhCanSetAppRestriction()) { + UserManager um = mContext.getSystemService(UserManager.class); + if (!um.isManagedProfile()) { + throw new SecurityException("The current user does not have a parent profile."); + } + } else { + if (!mService.isManagedProfile(admin)) { + throw new SecurityException("The current user does not have a parent profile."); + } } return new DevicePolicyManager(mContext, mService, true); } catch (RemoteException e) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d4589dc6d453..2002326d76bd 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -244,8 +244,8 @@ interface IDevicePolicyManager { void setDefaultSmsApplication(in ComponentName admin, String callerPackageName, String packageName, boolean parent); void setDefaultDialerApplication(String packageName); - void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings); - Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName); + void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings, in boolean parent); + Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in boolean parent); boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName); String getApplicationRestrictionsManagingPackage(in ComponentName admin); boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 4fa45be57a11..cca0b14904ca 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -191,6 +191,13 @@ flag { } flag { + name: "dmrh_can_set_app_restriction" + namespace: "enterprise" + description: "Allow DMRH to set application restrictions (both on the profile and the parent)" + bug: "328758346" +} + +flag { name: "allow_screen_brightness_control_on_cope" namespace: "enterprise" description: "Allow COPE admin to control screen brightness and timeout." diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java index 82f9aadba9f4..d24afabe95a4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -92,7 +92,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_VALUE)) { - values.add(parser.nextText().trim()); + values.add(parser.nextText()); count--; } } @@ -111,7 +111,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { restrictions.putParcelableArray(key, bundleList.toArray(new Bundle[bundleList.size()])); } else { - String value = parser.nextText().trim(); + String value = parser.nextText(); if (ATTR_TYPE_BOOLEAN.equals(valType)) { restrictions.putBoolean(key, Boolean.parseBoolean(value)); } else if (ATTR_TYPE_INTEGER.equals(valType)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f955b91136a3..ec9197f5343c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11502,10 +11502,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setApplicationRestrictions(ComponentName who, String callerPackage, - String packageName, Bundle restrictions) { + String packageName, Bundle restrictions, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS); + // This check is eventually made in UMS, checking here to fail early. + String validationResult = + FrameworkParsingPackageUtils.validateName(packageName, false, false); + if (validationResult != null) { + throw new IllegalArgumentException("Invalid package name: " + validationResult); + } + if (isUnicornFlagEnabled()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( who, @@ -11513,12 +11520,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUserId() ); - // This check is eventually made in UMS, checking here to fail early. - String validationResult = - FrameworkParsingPackageUtils.validateName(packageName, false, false); - if (validationResult != null) { - throw new IllegalArgumentException("Invalid package name: " + validationResult); - } if (restrictions == null || restrictions.isEmpty()) { mDevicePolicyEngine.removeLocalPolicy( @@ -11534,6 +11535,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // DO or PO + Preconditions.checkCallAuthorization( + (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + Preconditions.checkCallAuthorization(!parent, + "DO or PO cannot call this on parent"); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Delegates, or the DMRH. Only DMRH can call this on COPE parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + // DMRH caller uses policy engine, others still use legacy code path + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + if (restrictions == null || restrictions.isEmpty()) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + affectedUserId); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + new BundlePolicyValue(restrictions), + affectedUserId); + } + Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + changeIntent.setPackage(packageName); + changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId)); + } else { + mInjector.binderWithCleanCallingIdentity(() -> { + mUserManager.setApplicationRestrictions(packageName, restrictions, + caller.getUserHandle()); + }); + } } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -12865,7 +12917,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public Bundle getApplicationRestrictions(ComponentName who, String callerPackage, - String packageName) { + String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); if (isUnicornFlagEnabled()) { @@ -12884,6 +12936,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // Caller is DO or PO. They cannot call this on parent + Preconditions.checkCallAuthorization(!parent + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Caller is delegates or the DMRH. Only DMRH can call this on parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + affectedUserId); + if (!policies.containsKey(enforcingAdmin)) { + return Bundle.EMPTY; + } + return policies.get(enforcingAdmin).getValue(); + } else { + return mInjector.binderWithCleanCallingIdentity(() -> { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, + caller.getUserHandle()); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + }); + } + } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -15804,19 +15900,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (EnforcingAdmin admin : policies.keySet()) { restrictions.add(policies.get(admin).getValue()); } - if (!restrictions.isEmpty()) { - return restrictions; - } return mInjector.binderWithCleanCallingIdentity(() -> { - // Could be a device that has a DPC that hasn't migrated yet, so just return any + // Could be a device that has a DPC that hasn't migrated yet, so also return any // restrictions saved in userManager. Bundle bundle = mUserManager.getApplicationRestrictions( packageName, UserHandle.of(userId)); - if (bundle == null || bundle.isEmpty()) { - return new ArrayList<>(); + if (bundle != null && !bundle.isEmpty()) { + restrictions.add(bundle); } - return List.of(bundle); + return restrictions; }); } |