Loading core/java/android/app/admin/DevicePolicyManager.java +11 −4 Original line number Diff line number Diff line Loading @@ -3730,6 +3730,11 @@ public class DevicePolicyManager { * requires that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}. * <p> * When this policy is set by a device owner, profile owner of an organization-owned device or * an admin on the primary user, the device will be factory reset after too many incorrect * password attempts. When set by a profile owner or an admin on a secondary user or a managed * profile, only the corresponding user or profile will be wiped. * <p> * To implement any other policy (e.g. wiping data for a particular application only, erasing or * revoking credentials, or reporting the failure to a server), you should implement * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)} instead. Do not Loading Loading @@ -3798,10 +3803,12 @@ public class DevicePolicyManager { } /** * Returns the profile with the smallest maximum failed passwords for wipe, * for the given user. So for primary user, it might return the primary or * a managed profile. For a secondary user, it would be the same as the * user passed in. * Returns the user that will be wiped first when too many failed attempts are made to unlock * user {@code userHandle}. That user is either the same as {@code userHandle} or belongs to the * same profile group. When there is no such policy, returns {@code UserHandle.USER_NULL}. * E.g. managed profile user may be wiped as a result of failed primary profile password * attempts when using unified challenge. Primary user may be wiped as a result of failed * password attempts on the managed profile on an organization-owned device. * @hide Used only by Keyguard */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) Loading services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +16 −4 Original line number Diff line number Diff line Loading @@ -5343,7 +5343,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked( userHandle, parent); return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL; return admin != null ? getUserIdToWipeForFailedPasswords(admin) : UserHandle.USER_NULL; } } Loading @@ -5354,7 +5354,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * <li>this user and all profiles that don't have their own challenge otherwise. * </ul> * <p>If the policy for the primary and any other profile are equal, it returns the admin for * the primary profile. * the primary profile. Policy of a PO on an organization-owned device applies to the primary * profile. * Returns {@code null} if no participating admin has that policy set. */ private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked( Loading @@ -5373,7 +5374,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // We always favor the primary profile if several profiles have the same value set. int userId = admin.getUserHandle().getIdentifier(); final int userId = getUserIdToWipeForFailedPasswords(admin); if (count == 0 || count > admin.maximumFailedPasswordsForWipe || (count == admin.maximumFailedPasswordsForWipe && Loading Loading @@ -7170,7 +7171,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (wipeData && strictestAdmin != null) { final int userId = strictestAdmin.getUserHandle().getIdentifier(); final int userId = getUserIdToWipeForFailedPasswords(strictestAdmin); Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: " + strictestAdmin.info.getComponent().flattenToShortString() + ". Calling wipeData for user " + userId); Loading Loading @@ -7201,6 +7202,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } /** * Returns which user should be wiped if this admin's maximum filed password attempts policy is * violated. */ private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) { final int userId = admin.getUserHandle().getIdentifier(); final ComponentName component = admin.info.getComponent(); return isProfileOwnerOfOrganizationOwnedDevice(component, userId) ? getProfileParentId(userId) : userId; } @Override public void reportSuccessfulPasswordAttempt(int userHandle) { enforceFullCrossUsersPermission(userHandle); Loading services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +81 −2 Original line number Diff line number Diff line Loading @@ -4535,6 +4535,86 @@ public class DevicePolicyManagerTest extends DpmTestBase { .removeUserEvenWhenDisallowed(anyInt()); } public void testMaximumFailedDevicePasswordAttemptsReachedOrgOwnedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(getServices().iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(admin1)); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null)); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); // Check that primary will be wiped as a result of failed primary user unlock attempts. assertEquals(UserHandle.USER_SYSTEM, dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // For managed profile on an organization owned device, the whole device should be wiped. verify(getServices().recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), /*wipeEuicc=*/ eq(false)); } public void testMaximumFailedProfilePasswordAttemptsReachedOrgOwnedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(getServices().iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); doReturn(true).when(getServices().lockPatternUtils) .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); // Configure separate challenge. configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); assertEquals(0, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, MANAGED_PROFILE_USER_ID)); // Check that the policy is not affecting primary profile challenge. assertEquals(UserHandle.USER_NULL, dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); // Check that primary will be wiped as a result of failed profile unlock attempts. assertEquals(UserHandle.USER_SYSTEM, dpm.getProfileWithMinimumFailedPasswordsForWipe(MANAGED_PROFILE_USER_ID)); // Simulate three failed attempts at solving the separate challenge. dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); // For managed profile on an organization owned device, the whole device should be wiped. verify(getServices().recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), /*wipeEuicc=*/ eq(false)); } public void testGetPermissionGrantState() throws Exception { final String permission = "some.permission"; final String app1 = "com.example.app1"; Loading Loading @@ -5770,8 +5850,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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); mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID); configureContextForAccess(mServiceContext, true); runAsCaller(mServiceContext, dpms, dpm -> { Loading Loading
core/java/android/app/admin/DevicePolicyManager.java +11 −4 Original line number Diff line number Diff line Loading @@ -3730,6 +3730,11 @@ public class DevicePolicyManager { * requires that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}. * <p> * When this policy is set by a device owner, profile owner of an organization-owned device or * an admin on the primary user, the device will be factory reset after too many incorrect * password attempts. When set by a profile owner or an admin on a secondary user or a managed * profile, only the corresponding user or profile will be wiped. * <p> * To implement any other policy (e.g. wiping data for a particular application only, erasing or * revoking credentials, or reporting the failure to a server), you should implement * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)} instead. Do not Loading Loading @@ -3798,10 +3803,12 @@ public class DevicePolicyManager { } /** * Returns the profile with the smallest maximum failed passwords for wipe, * for the given user. So for primary user, it might return the primary or * a managed profile. For a secondary user, it would be the same as the * user passed in. * Returns the user that will be wiped first when too many failed attempts are made to unlock * user {@code userHandle}. That user is either the same as {@code userHandle} or belongs to the * same profile group. When there is no such policy, returns {@code UserHandle.USER_NULL}. * E.g. managed profile user may be wiped as a result of failed primary profile password * attempts when using unified challenge. Primary user may be wiped as a result of failed * password attempts on the managed profile on an organization-owned device. * @hide Used only by Keyguard */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) Loading
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +16 −4 Original line number Diff line number Diff line Loading @@ -5343,7 +5343,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked( userHandle, parent); return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL; return admin != null ? getUserIdToWipeForFailedPasswords(admin) : UserHandle.USER_NULL; } } Loading @@ -5354,7 +5354,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * <li>this user and all profiles that don't have their own challenge otherwise. * </ul> * <p>If the policy for the primary and any other profile are equal, it returns the admin for * the primary profile. * the primary profile. Policy of a PO on an organization-owned device applies to the primary * profile. * Returns {@code null} if no participating admin has that policy set. */ private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked( Loading @@ -5373,7 +5374,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // We always favor the primary profile if several profiles have the same value set. int userId = admin.getUserHandle().getIdentifier(); final int userId = getUserIdToWipeForFailedPasswords(admin); if (count == 0 || count > admin.maximumFailedPasswordsForWipe || (count == admin.maximumFailedPasswordsForWipe && Loading Loading @@ -7170,7 +7171,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (wipeData && strictestAdmin != null) { final int userId = strictestAdmin.getUserHandle().getIdentifier(); final int userId = getUserIdToWipeForFailedPasswords(strictestAdmin); Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: " + strictestAdmin.info.getComponent().flattenToShortString() + ". Calling wipeData for user " + userId); Loading Loading @@ -7201,6 +7202,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } /** * Returns which user should be wiped if this admin's maximum filed password attempts policy is * violated. */ private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) { final int userId = admin.getUserHandle().getIdentifier(); final ComponentName component = admin.info.getComponent(); return isProfileOwnerOfOrganizationOwnedDevice(component, userId) ? getProfileParentId(userId) : userId; } @Override public void reportSuccessfulPasswordAttempt(int userHandle) { enforceFullCrossUsersPermission(userHandle); Loading
services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +81 −2 Original line number Diff line number Diff line Loading @@ -4535,6 +4535,86 @@ public class DevicePolicyManagerTest extends DpmTestBase { .removeUserEvenWhenDisallowed(anyInt()); } public void testMaximumFailedDevicePasswordAttemptsReachedOrgOwnedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(getServices().iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(admin1)); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null)); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); // Check that primary will be wiped as a result of failed primary user unlock attempts. assertEquals(UserHandle.USER_SYSTEM, dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // For managed profile on an organization owned device, the whole device should be wiped. verify(getServices().recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), /*wipeEuicc=*/ eq(false)); } public void testMaximumFailedProfilePasswordAttemptsReachedOrgOwnedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(getServices().iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); doReturn(true).when(getServices().lockPatternUtils) .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); // Configure separate challenge. configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); assertEquals(0, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, MANAGED_PROFILE_USER_ID)); // Check that the policy is not affecting primary profile challenge. assertEquals(UserHandle.USER_NULL, dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); // Check that primary will be wiped as a result of failed profile unlock attempts. assertEquals(UserHandle.USER_SYSTEM, dpm.getProfileWithMinimumFailedPasswordsForWipe(MANAGED_PROFILE_USER_ID)); // Simulate three failed attempts at solving the separate challenge. dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); // For managed profile on an organization owned device, the whole device should be wiped. verify(getServices().recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), /*wipeEuicc=*/ eq(false)); } public void testGetPermissionGrantState() throws Exception { final String permission = "some.permission"; final String app1 = "com.example.app1"; Loading Loading @@ -5770,8 +5850,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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); mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID); configureContextForAccess(mServiceContext, true); runAsCaller(mServiceContext, dpms, dpm -> { Loading