Wiping and relinquishing org-owned devices
Add the following functionality, on devices with a managed profile
created during provisionining (and as such, considered
organization-owned):
* Let the Profile Owner relinquish a device by calling
DevicePolicyManager.wipeData. The device then transitions
to a fully-personal device.
* Let the Profile Owner wipe the entire device by calling
wipeData on the parent profile DevicePolicyManager instance.
Bug: 138709470
Test: Manual with TestDPC.
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest
Test: atest com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testDeviceIdAttestationForProfileOwner
Change-Id: If3cc9741079592cb07bc1ef5ccca8fb2b57a52e9
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 99f484e..ea987c0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5568,6 +5568,13 @@
}
}
+ private void enforceProfileOwnerOfCorpOwnedDevice(ActiveAdmin admin) {
+ if (!isProfileOwnerOfOrganizationOwnedDevicte(admin)) {
+ throw new SecurityException(String.format("Provided admin %s is either not a profile "
+ + "owner or not on a corporate-owned device.", admin));
+ }
+ }
+
@Override
public boolean approveCaCert(String alias, int userId, boolean approval) {
enforceManageUsers();
@@ -6613,27 +6620,83 @@
}
@Override
- public void wipeDataWithReason(int flags, String wipeReasonForUser) {
+ public void wipeDataWithReason(int flags, String wipeReasonForUser,
+ boolean calledOnParentInstance) {
if (!mHasFeature) {
return;
}
- Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
+
enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
final ActiveAdmin admin;
synchronized (getLockObject()) {
admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
}
+
+ if (admin == null) {
+ throw new SecurityException(String.format("No active admin for user %d",
+ mInjector.userHandleGetCallingUserId()));
+ }
+
+ boolean calledByProfileOwnerOnOrgOwnedDevice =
+ isProfileOwnerOfOrganizationOwnedDevicte(admin);
+
+ if (calledOnParentInstance && !calledByProfileOwnerOnOrgOwnedDevice) {
+ throw new SecurityException("Wiping the entire device can only be done by a profile"
+ + "owner on organization-owned device.");
+ }
+
+ if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
+ if (!isDeviceOwner(admin) && !calledByProfileOwnerOnOrgOwnedDevice) {
+ throw new SecurityException(
+ "Only device owners or proflie owners of organization-owned device"
+ + " can set WIPE_RESET_PROTECTION_DATA");
+ }
+ }
+
+ if (TextUtils.isEmpty(wipeReasonForUser)) {
+ if (calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance) {
+ wipeReasonForUser = mContext.getString(R.string.device_ownership_relinquished);
+ } else {
+ wipeReasonForUser = mContext.getString(
+ R.string.work_profile_deleted_description_dpm_wipe);
+ }
+ }
+
+ int userId = admin.getUserHandle().getIdentifier();
+ if (calledByProfileOwnerOnOrgOwnedDevice) {
+ // When wipeData is called on the parent instance, it implies wiping the entire device.
+ if (calledOnParentInstance) {
+ userId = UserHandle.USER_SYSTEM;
+ } else {
+ // when wipeData is _not_ called on the parent instance, it implies relinquishing
+ // control over the device, wiping only the work profile. So the user restriction
+ // on profile removal needs to be removed first.
+
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ // Clear restriction as user.
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false,
+ UserHandle.SYSTEM);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ }
+
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.WIPE_DATA_WITH_REASON)
.setAdmin(admin.info.getComponent())
.setInt(flags)
.write();
- String internalReason = "DevicePolicyManager.wipeDataWithReason() from "
- + admin.info.getComponent().flattenToShortString();
+ String internalReason = String.format(
+ "DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
+ admin.info.getComponent().flattenToShortString(),
+ calledByProfileOwnerOnOrgOwnedDevice);
+
wipeDataNoLock(
- admin.info.getComponent(), flags, internalReason, wipeReasonForUser,
- admin.getUserHandle().getIdentifier());
+ admin.info.getComponent(), flags, internalReason, wipeReasonForUser, userId);
}
private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
@@ -6657,10 +6720,6 @@
}
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- if (!isDeviceOwner(admin, userId)) {
- throw new SecurityException(
- "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
- }
PersistentDataBlockManager manager = (PersistentDataBlockManager)
mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
if (manager != null) {
@@ -7954,6 +8013,35 @@
}
}
+ /**
+ * Returns true if the provided {@code admin} is a profile owner and the profile is marked
+ * as organization-owned.
+ * The {@code admin} parameter must be obtained by the service by calling
+ * {@code getActiveAdminForCallerLocked} or one of the similar variants, not caller-supplied
+ * input.
+ */
+ private boolean isProfileOwnerOfOrganizationOwnedDevicte(@Nullable ActiveAdmin admin) {
+ if (admin == null) {
+ return false;
+ }
+
+ final int adminUserId = admin.getUserHandle().getIdentifier();
+
+ if (!isProfileOwner(admin.info.getComponent(), adminUserId)) {
+ Slog.w(LOG_TAG, String.format("%s is not profile owner of user %d",
+ admin.info.getComponent(), adminUserId));
+ return false;
+ }
+
+ if (!canProfileOwnerAccessDeviceIds(adminUserId)) {
+ Slog.w(LOG_TAG, String.format("Profile owner of user %d does not own the device.",
+ adminUserId));
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) {
if (!mHasFeature) {
@@ -12614,6 +12702,24 @@
Slog.i(LOG_TAG, String.format("Granting Device ID access to %s, for user %d",
who.flattenToString(), userId));
+ // First, set restriction on removing the profile.
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ // Clear restriction as user.
+ UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
+ if (!parentUser.isSystem()) {
+ throw new IllegalStateException(
+ String.format("Only the profile owner of a managed profile on the"
+ + " primary user can be granted access to device identifiers, not"
+ + " on user %d", parentUser.getIdentifier()));
+ }
+
+ mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
+ parentUser);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+
// setProfileOwnerCanAccessDeviceIds will trigger writing of the profile owner
// data, no need to do it manually.
mOwners.setProfileOwnerCanAccessDeviceIds(userId);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f270724..eef77ee 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5162,11 +5162,14 @@
configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE);
}
- private static void configureContextForAccess(DpmMockContext context, boolean granted) {
+ private void configureContextForAccess(DpmMockContext context, boolean granted) {
when(context.spiedContext.checkCallingPermission(
android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS))
.thenReturn(granted ? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED);
+
+ when(getServices().userManager.getProfileParent(any()))
+ .thenReturn(UserHandle.SYSTEM);
}
public void testGrantDeviceIdsAccess_byAuthorizedManagedProvisioning() throws Exception {
@@ -5433,6 +5436,9 @@
}
private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
+ when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
+ .thenReturn(UserHandle.SYSTEM);
+
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid =
UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);