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

Commit ca493335 authored by Momoko Hattori's avatar Momoko Hattori Committed by Android (Google) Code Review
Browse files

Merge "Reject last full admin user in revokeUserAdmin() under a flag" into main

parents 3a9911d3 aba2df76
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ public class UserJourneyLogger {
    public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
    public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
    public static final int ERROR_CODE_INVALID_USER_TYPE = 7;
    public static final int ERROR_CODE_USER_IS_LAST_ADMIN = 8;

    @IntDef(prefix = {"ERROR_CODE"}, value = {
            ERROR_CODE_UNSPECIFIED,
@@ -78,7 +79,8 @@ public class UserJourneyLogger {
            ERROR_CODE_USER_ALREADY_AN_ADMIN,
            ERROR_CODE_USER_IS_NOT_AN_ADMIN,
            ERROR_CODE_INVALID_SESSION_ID,
            ERROR_CODE_INVALID_USER_TYPE
            ERROR_CODE_INVALID_USER_TYPE,
            ERROR_CODE_USER_IS_LAST_ADMIN,
    })
    public @interface UserJourneyErrorCode {
    }
+30 −15
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_USER_TYPE;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_LAST_ADMIN;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_DEMOTE_MAIN_USER;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_PROMOTE_MAIN_USER;
@@ -2337,14 +2338,14 @@ public class UserManagerService extends IUserManager.Stub {
                            currentUserId, userId, /* userType */ "", /* userFlags */ -1);
                    return;
                } else if (user.info.isAdmin()) {
                    // Exit if the user is already an Admin.
                    // Exit if the user is already an admin.
                    mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId,
                        user.info, USER_JOURNEY_GRANT_ADMIN,
                        ERROR_CODE_USER_ALREADY_AN_ADMIN);
                    return;
                } else if (user.info.isProfile() || user.info.isGuest()
                        || user.info.isRestricted()) {
                    // Profiles, guest users or restricted profiles cannot become an Admin.
                    // Profiles, guest users or restricted profiles cannot become an admin.
                    mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId,
                            user.info, USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_INVALID_USER_TYPE);
                    return;
@@ -2376,15 +2377,21 @@ public class UserManagerService extends IUserManager.Stub {
                            USER_JOURNEY_REVOKE_ADMIN, currentUserId, userId, "", -1);
                    return;
                } else if (!user.info.isAdmin()) {
                    // Exit if user is not an Admin.
                    // Exit if user is not an admin.
                    mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info,
                            USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN);
                    return;
                } else if ((user.info.flags & UserInfo.FLAG_SYSTEM) != 0) {
                    // System user must always be an Admin.
                    // System user cannot lose its admin status.
                    mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info,
                            USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_INVALID_USER_TYPE);
                    return;
                } else if (isNonRemovableLastAdminUserLU(user.info)) {
                    // This is the last admin user and this device requires that it not lose its
                    // admin status.
                    mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info,
                            USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_LAST_ADMIN);
                    return;
                }
                user.info.flags ^= UserInfo.FLAG_ADMIN;
                writeUserLP(user);
@@ -7043,18 +7050,11 @@ public class UserManagerService extends IUserManager.Stub {
                    msg);
            return UserManager.REMOVE_RESULT_ALREADY_BEING_REMOVED;
        }
        if (android.multiuser.Flags.disallowRemovingLastAdminUser()) {
            if (getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser)) {
                // For HSUM, the headless system user is currently flagged as an admin user now.
                // Thus we must exclude it when checking for the last admin user, and only consider
                // full admin users. b/419105275 will investigate not making HSU an admin.
                if (isLastFullAdminUserLU(userData.info)) {
                    Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.",
                            userId, msg);
        if (isNonRemovableLastAdminUserLU(userData.info)) {
            Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.", userId,
                    msg);
            return UserManager.REMOVE_RESULT_ERROR_LAST_ADMIN_USER;
        }
            }
        }
        return UserManager.REMOVE_RESULT_USER_IS_REMOVABLE;
    }

@@ -8841,6 +8841,21 @@ public class UserManagerService extends IUserManager.Stub {
        return userInfo.isMain() && isMainUserPermanentAdmin();
    }

    /**
     * Returns true if we must not delete this user or revoke its admin status because it is the
     * last admin user on a device that requires there to always be at least one admin.
     */
    @GuardedBy("mUsersLock")
    private boolean isNonRemovableLastAdminUserLU(UserInfo userInfo) {
        return android.multiuser.Flags.disallowRemovingLastAdminUser()
                && getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser)
                // For HSUM, the headless system user is currently flagged as an admin user now.
                // Thus we must exclude it when checking for the last admin user, and only consider
                // full admin users.
                // TODO(b/419105275): Investigate not making HSU an admin.
                && isLastFullAdminUserLU(userInfo);
    }

    /** Returns if the user is the last admin user that is a full user. */
    @GuardedBy("mUsersLock")
    @VisibleForTesting
+25 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION;
import static android.multiuser.Flags.FLAG_DEMOTE_MAIN_USER;
import static android.multiuser.Flags.FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER;
import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
import static android.multiuser.Flags.FLAG_LOGOUT_USER_API;
import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE;
@@ -236,10 +237,10 @@ public final class UserManagerServiceMockedTest {
        doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
        mockIsLowRamDevice(false);

        // Called when getting boot user. config_hsumBootStrategy is 0 by default.
        mSpyResources = spy(mSpiedContext.getResources());
        when(mSpiedContext.getResources()).thenReturn(mSpyResources);
        mockHsumBootStrategy(BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER);
        mockDisallowRemovingLastAdminUser(false);

        doReturn(mSpyResources).when(Resources::getSystem);

@@ -1822,6 +1823,19 @@ public final class UserManagerServiceMockedTest {
        assertThat(mUsers.get(UserHandle.USER_SYSTEM).info.isAdmin()).isTrue();
    }

    @Test
    @RequiresFlagsEnabled(FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER)
    public void testRevokeUserAdminFailsForLastFullAdmin() {
        mockDisallowRemovingLastAdminUser(true);
        // Mark system user as headless so that it is not a full admin user.
        setSystemUserHeadless(true);
        addAdminUser(USER_ID);

        mUms.revokeUserAdmin(USER_ID);

        assertThat(mUsers.get(USER_ID).info.isAdmin()).isTrue();
    }

    /**
     * Returns true if the user's XML file has Default restrictions
     * @param userId Id of the user.
@@ -1958,6 +1972,16 @@ public final class UserManagerServiceMockedTest {
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
    }

    private void mockDisallowRemovingLastAdminUser(boolean disallow) {
        boolean previousValue = mSpyResources
                .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser);
        Log.d(TAG, "mockDisallowRemovingLastAdminUser(): will return " + disallow + " instead of "
                + previousValue);
        doReturn(disallow)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser);
    }

    private void mockUserIsInCall(boolean isInCall) {
        when(mTelecomManager.isInCall()).thenReturn(isInCall);
    }