Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f4cce6f authored by Kasia Krejszeff's avatar Kasia Krejszeff
Browse files

Remove private profiles even when DISALLOW_REMOVE_USER is enabled.

Test: atest UserManagerTest
Flag: android.multiuser.ignore_restrictions_when_deleting_private_profile
Bug: 350953833
Change-Id: I3e66337db3e9ec7e1139d5add3ce0294111eba79
parent 5ade4db7
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -533,3 +533,13 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "ignore_restrictions_when_deleting_private_profile"
  namespace: "multiuser"
  description: "Ignore any user restrictions when deleting private profiles."
  bug: "350953833"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+5 −3
Original line number Diff line number Diff line
@@ -772,9 +772,10 @@ public class UserManager {
    public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";

    /**
     * When set on the admin user this specifies if the user can remove users.
     * When set on the admin user this specifies if the user can remove secondary users. Managed
     * profiles and private profiles can still be removed even if this is set on the admin user.
     * When set on a non-admin secondary user, this specifies if the user can remove itself.
     * This restriction has no effect on managed profiles.
     * This restriction has no effect when set on managed profiles.
     * The default value is <code>false</code>.
     *
     * <p>Holders of the permission
@@ -793,7 +794,8 @@ public class UserManager {
     * Specifies if managed profiles of this user can be removed, other than by its profile owner.
     * The default value is <code>false</code>.
     * <p>
     * This restriction has no effect on managed profiles.
     * This restriction has no effect on managed profiles, and this restriction does not block the
     * removal of private profiles of this user.
     *
     * <p>Key for user restrictions.
     * <p>Type: Boolean
+31 −12
Original line number Diff line number Diff line
@@ -195,6 +195,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -6261,9 +6262,11 @@ public class UserManagerService extends IUserManager.Stub {
        Slog.i(LOG_TAG, "removeUser u" + userId);
        checkCreateUsersPermission("Only the system can remove users");

        final String restriction = getUserRemovalRestriction(userId);
        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
            Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
        final Optional<String> restrictionOptional = getUserRemovalRestrictionOptional(userId);
        if (!restrictionOptional.isEmpty()
                && getUserRestrictions(UserHandle.getCallingUserId())
                        .getBoolean(restrictionOptional.get(), false)) {
            Slog.w(LOG_TAG, "Cannot remove user. " + restrictionOptional.get() + " is enabled.");
            return false;
        }
        if (mCurrentBootPhase < SystemService.PHASE_ACTIVITY_MANAGER_READY) {
@@ -6335,18 +6338,30 @@ public class UserManagerService extends IUserManager.Stub {
    }

    /**
     * Returns the string name of the restriction to check for user removal. The restriction name
     * varies depending on whether the user is a managed profile.
     * Returns an optional string name of the restriction to check for user removal. The restriction
     * name varies depending on whether the user is a managed profile.
     *
     * <p>If the flag android.multiuser.ignore_restrictions_when_deleting_private_profile is enabled
     * and the user is a private profile (i.e. has no removal restrictions) the method will return
     * {@code Optional.empty()}.
     */
    private String getUserRemovalRestriction(@UserIdInt int userId) {
    private Optional<String> getUserRemovalRestrictionOptional(@UserIdInt int userId) {
        final boolean isPrivateProfile;
        final boolean isManagedProfile;
        final UserInfo userInfo;
        synchronized (mUsersLock) {
            userInfo = getUserInfoLU(userId);
        }
        isPrivateProfile = userInfo != null && userInfo.isPrivateProfile();
        isManagedProfile = userInfo != null && userInfo.isManagedProfile();
        return isManagedProfile
                ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
        if (android.multiuser.Flags.ignoreRestrictionsWhenDeletingPrivateProfile()
                && isPrivateProfile) {
            return Optional.empty();
        }
        return Optional.of(
                isManagedProfile
                        ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
                        : UserManager.DISALLOW_REMOVE_USER);
    }

    private boolean removeUserUnchecked(@UserIdInt int userId) {
@@ -6455,9 +6470,13 @@ public class UserManagerService extends IUserManager.Stub {
        checkCreateUsersPermission("Only the system can remove users");

        if (!overrideDevicePolicy) {
            final String restriction = getUserRemovalRestriction(userId);
            if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
                Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
            final Optional<String> restrictionOptional = getUserRemovalRestrictionOptional(userId);
            if (!restrictionOptional.isEmpty()
                    && getUserRestrictions(UserHandle.getCallingUserId())
                            .getBoolean(restrictionOptional.get(), false)) {
                Slog.w(
                        LOG_TAG,
                        "Cannot remove user. " + restrictionOptional.get() + " is enabled.");
                return UserManager.REMOVE_RESULT_ERROR_USER_RESTRICTION;
            }
        }
+73 −0
Original line number Diff line number Diff line
@@ -537,6 +537,79 @@ public final class UserManagerTest {
        removeUser(testUser.id);
    }

    @MediumTest
    @Test
    public void testRemoveUser_shouldRemovePrivateUser() {
        UserInfo privateProfileUser =
                createProfileForUser(
                        "Private profile",
                        UserManager.USER_TYPE_PROFILE_PRIVATE,
                        mUserManager.getMainUser().getIdentifier());
        assertThat(privateProfileUser).isNotNull();
        assertThat(hasUser(privateProfileUser.id)).isTrue();

        removeUser(privateProfileUser.id);

        assertThat(hasUser(privateProfileUser.id)).isFalse();
    }

    @MediumTest
    @Test
    @RequiresFlagsEnabled(
            android.multiuser.Flags.FLAG_IGNORE_RESTRICTIONS_WHEN_DELETING_PRIVATE_PROFILE)
    public void testRemoveUser_shouldRemovePrivateUser_withDisallowRemoveUserRestriction() {
        UserHandle mainUser = mUserManager.getMainUser();
        mUserManager.setUserRestriction(
                UserManager.DISALLOW_REMOVE_USER, /* value= */ true, mainUser);
        try {
            UserInfo privateProfileUser =
                    createProfileForUser(
                            "Private profile",
                            UserManager.USER_TYPE_PROFILE_PRIVATE,
                            mainUser.getIdentifier());
            assertThat(privateProfileUser).isNotNull();
            assertThat(hasUser(privateProfileUser.id)).isTrue();
            removeUser(privateProfileUser.id);

            assertThat(hasUser(privateProfileUser.id)).isFalse();
        } finally {
            mUserManager.setUserRestriction(
                    UserManager.DISALLOW_REMOVE_USER, /* value= */ false, mainUser);
        }
    }

    @MediumTest
    @Test
    public void testRemoveUser_withDisallowRemoveUserRestrictionAndMultipleUsersPresent() {
        UserInfo privateProfileUser =
                createProfileForUser(
                        "Private profile",
                        UserManager.USER_TYPE_PROFILE_PRIVATE,
                        mUserManager.getMainUser().getIdentifier());
        assertThat(privateProfileUser).isNotNull();
        assertThat(hasUser(privateProfileUser.id)).isTrue();
        UserInfo testUser = createUser("TestUser", /* flags= */ 0);
        assertThat(testUser).isNotNull();
        assertThat(hasUser(testUser.id)).isTrue();
        UserHandle mainUser = mUserManager.getMainUser();
        mUserManager.setUserRestriction(
                UserManager.DISALLOW_REMOVE_USER, /* value= */ true, mainUser);
        try {
            assertThat(
                    mUserManager.removeUserWhenPossible(
                            testUser.getUserHandle(), /* overrideDevicePolicy= */ false))
                    .isEqualTo(UserManager.REMOVE_RESULT_ERROR_USER_RESTRICTION);

            // Non private profile users should be prevented from being removed.
            assertThat(mUserManager.removeUser(testUser.id)).isEqualTo(false);

            assertThat(hasUser(testUser.id)).isTrue();
        } finally {
            mUserManager.setUserRestriction(
                    UserManager.DISALLOW_REMOVE_USER, /* value= */ false, mainUser);
        }
    }

    @MediumTest
    @Test
    public void testRemoveUserShouldNotRemoveTargetUser_DuringUserSwitch() {