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

Commit 5e7e0670 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Allow DO/PO to be installed with certain preconfigured accounts.

- Non-test-only DO/PO still can't be installed when there are
accounts.

- Test-only DO/PO can be installed even when there are accounts,
as long as all the accounts have the
"android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED" feature.
Some authenticators claim to have any features, so to detect it,
we also check android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED
and disallow installing if any of the accounts have it.

- Also add logs on certain important events in DPMS.

Bug 28928996

Change-Id: I62efce10e9cc22e994ea8cae91a4fafcce25dd77
parent 03f206a2
Loading
Loading
Loading
Loading
+27 −0
Original line number Original line Diff line number Diff line
@@ -1275,6 +1275,33 @@ public class DevicePolicyManager {
     */
     */
    public static final int PASSWORD_QUALITY_MANAGED = 0x80000;
    public static final int PASSWORD_QUALITY_MANAGED = 0x80000;


    /**
     * @hide
     *
     * adb shell dpm set-{device,profile}-owner will normally not allow installing an owner to
     * a user with accounts.  {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED}
     * and {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED} are the account features
     * used by authenticator to exempt their accounts from this:
     *
     * <ul>
     *     <li>Non-test-only DO/PO still can't be installed when there are accounts.
     *     <p>In order to make an apk test-only, add android:testOnly="true" to the
     *     &lt;application&gt; tag in the manifest.
     *
     *     <li>Test-only DO/PO can be installed even when there are accounts, as long as all the
     *     accounts have the {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED} feature.
     *     Some authenticators claim to have any features, so to detect it, we also check
     *     {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED} and disallow installing
     *     if any of the accounts have it.
     * </ul>
     */
    public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED =
            "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";

    /** @hide See {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED} */
    public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED =
            "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";

    /**
    /**
     * Called by an application that is administering the device to set the password restrictions it
     * Called by an application that is administering the device to set the password restrictions it
     * is imposing. After setting this, the user will not be able to enter a new password that is
     * is imposing. After setting this, the user will not be able to enter a new password that is
+116 −25
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@ import static org.xmlpull.v1.XmlPullParser.TEXT;


import android.Manifest.permission;
import android.Manifest.permission;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManager;
import android.annotation.IntDef;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
@@ -2943,19 +2944,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        enforceShell("forceRemoveActiveAdmin");
        enforceShell("forceRemoveActiveAdmin");
        long ident = mInjector.binderClearCallingIdentity();
        long ident = mInjector.binderClearCallingIdentity();
        try {
        try {
            final ApplicationInfo ai;
            if (!isPackageTestOnly(adminReceiver.getPackageName(), userHandle)) {
            try {
                throw new SecurityException("Attempt to remove non-test admin "
                ai = mIPackageManager.getApplicationInfo(adminReceiver.getPackageName(),
                        0, userHandle);
            } catch (RemoteException e) {
                throw new IllegalStateException(e);
            }
            if (ai == null) {
                throw new IllegalStateException("Couldn't find package to remove admin "
                        + adminReceiver.getPackageName() + " " + userHandle);
            }
            if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
                throw new SecurityException("Attempt to remove non-test admin " + adminReceiver
                        + adminReceiver + " " + userHandle);
                        + adminReceiver + " " + userHandle);
            }
            }
            // If admin is a device or profile owner tidy that up first.
            // If admin is a device or profile owner tidy that up first.
@@ -2971,11 +2961,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            }
            }
            // Remove the admin skipping sending the broadcast.
            // Remove the admin skipping sending the broadcast.
            removeAdminArtifacts(adminReceiver, userHandle);
            removeAdminArtifacts(adminReceiver, userHandle);
            Slog.i(LOG_TAG, "Admin " + adminReceiver + " removed from user " + userHandle);
        } finally {
        } finally {
            mInjector.binderRestoreCallingIdentity(ident);
            mInjector.binderRestoreCallingIdentity(ident);
        }
        }
    }
    }


    private boolean isPackageTestOnly(String packageName, int userHandle) {
        final ApplicationInfo ai;
        try {
            ai = mIPackageManager.getApplicationInfo(packageName,
                    (PackageManager.MATCH_DIRECT_BOOT_AWARE
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
        } catch (RemoteException e) {
            throw new IllegalStateException(e);
        }
        if (ai == null) {
            throw new IllegalStateException("Couldn't find package to remove admin "
                    + packageName + " " + userHandle);
        }
        return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
    }

    private void enforceShell(String method) {
    private void enforceShell(String method) {
        final int callingUid = Binder.getCallingUid();
        final int callingUid = Binder.getCallingUid();
        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
@@ -4514,7 +4521,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     * not installed and therefore not available.
     * not installed and therefore not available.
     *
     *
     * @throws SecurityException if the caller is not a profile or device owner.
     * @throws SecurityException if the caller is not a profile or device owner.
     * @throws UnsupportedException if the package does not support being set as always-on.
     * @throws UnsupportedOperationException if the package does not support being set as always-on.
     */
     */
    @Override
    @Override
    public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown)
    public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown)
@@ -5710,7 +5717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    + " for device owner");
                    + " for device owner");
        }
        }
        synchronized (this) {
        synchronized (this) {
            enforceCanSetDeviceOwnerLocked(userId);
            enforceCanSetDeviceOwnerLocked(admin, userId);
            if (getActiveAdminUncheckedLocked(admin, userId) == null
            if (getActiveAdminUncheckedLocked(admin, userId) == null
                    || getUserData(userId).mRemovingAdmins.contains(admin)) {
                    || getUserData(userId).mRemovingAdmins.contains(admin)) {
                throw new IllegalArgumentException("Not active admin: " + admin);
                throw new IllegalArgumentException("Not active admin: " + admin);
@@ -5742,6 +5749,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            } finally {
            } finally {
                mInjector.binderRestoreCallingIdentity(ident);
                mInjector.binderRestoreCallingIdentity(ident);
            }
            }
            Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
            return true;
            return true;
        }
        }
    }
    }
@@ -5863,6 +5871,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            } finally {
            } finally {
                mInjector.binderRestoreCallingIdentity(ident);
                mInjector.binderRestoreCallingIdentity(ident);
            }
            }
            Slog.i(LOG_TAG, "Device owner removed: " + deviceOwnerComponent);
        }
        }
    }
    }


@@ -5898,7 +5907,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    + " not installed for userId:" + userHandle);
                    + " not installed for userId:" + userHandle);
        }
        }
        synchronized (this) {
        synchronized (this) {
            enforceCanSetProfileOwnerLocked(userHandle);
            enforceCanSetProfileOwnerLocked(who, userHandle);


            if (getActiveAdminUncheckedLocked(who, userHandle) == null
            if (getActiveAdminUncheckedLocked(who, userHandle) == null
                    || getUserData(userHandle).mRemovingAdmins.contains(who)) {
                    || getUserData(userHandle).mRemovingAdmins.contains(who)) {
@@ -5907,6 +5916,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {


            mOwners.setProfileOwner(who, ownerName, userHandle);
            mOwners.setProfileOwner(who, ownerName, userHandle);
            mOwners.writeProfileOwner(userHandle);
            mOwners.writeProfileOwner(userHandle);
            Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
            return true;
            return true;
        }
        }
    }
    }
@@ -5931,6 +5941,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            } finally {
            } finally {
                mInjector.binderRestoreCallingIdentity(ident);
                mInjector.binderRestoreCallingIdentity(ident);
            }
            }
            Slog.i(LOG_TAG, "Profile owner " + who + " removed from user " + userId);
        }
        }
    }
    }


@@ -6207,9 +6218,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     * The profile owner can only be set before the user setup phase has completed,
     * The profile owner can only be set before the user setup phase has completed,
     * except for:
     * except for:
     * - SYSTEM_UID
     * - SYSTEM_UID
     * - adb if there are not accounts.
     * - adb if there are no accounts. (But see {@link #hasIncompatibleAccounts})
     */
     */
    private void enforceCanSetProfileOwnerLocked(int userHandle) {
    private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle) {
        UserInfo info = getUserInfo(userHandle);
        UserInfo info = getUserInfo(userHandle);
        if (info == null) {
        if (info == null) {
            // User doesn't exist.
            // User doesn't exist.
@@ -6229,8 +6240,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        }
        }
        int callingUid = mInjector.binderGetCallingUid();
        int callingUid = mInjector.binderGetCallingUid();
        if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
        if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
            if (hasUserSetupCompleted(userHandle) &&
            if (hasUserSetupCompleted(userHandle)
                    AccountManager.get(mContext).getAccountsAsUser(userHandle).length > 0) {
                    && hasIncompatibleAccounts(userHandle, owner)) {
                throw new IllegalStateException("Not allowed to set the profile owner because "
                throw new IllegalStateException("Not allowed to set the profile owner because "
                        + "there are already some accounts on the profile");
                        + "there are already some accounts on the profile");
            }
            }
@@ -6247,14 +6258,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
     * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
     * permission.
     * permission.
     */
     */
    private void enforceCanSetDeviceOwnerLocked(int userId) {
    private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId) {
        int callingUid = mInjector.binderGetCallingUid();
        int callingUid = mInjector.binderGetCallingUid();
        boolean isAdb = callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
        boolean isAdb = callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
        if (!isAdb) {
        if (!isAdb) {
            enforceCanManageProfileAndDeviceOwners();
            enforceCanManageProfileAndDeviceOwners();
        }
        }


        final int code = checkSetDeviceOwnerPreCondition(userId, isAdb);
        final int code = checkSetDeviceOwnerPreCondition(owner, userId, isAdb);
        switch (code) {
        switch (code) {
            case CODE_OK:
            case CODE_OK:
                return;
                return;
@@ -8472,7 +8483,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     * except for adb command if no accounts or additional users are present on the device.
     * except for adb command if no accounts or additional users are present on the device.
     */
     */
    private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreCondition(
    private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreCondition(
            int deviceOwnerUserId, boolean isAdb) {
            @Nullable ComponentName owner, int deviceOwnerUserId, boolean isAdb) {
        if (mOwners.hasDeviceOwner()) {
        if (mOwners.hasDeviceOwner()) {
            return CODE_HAS_DEVICE_OWNER;
            return CODE_HAS_DEVICE_OWNER;
        }
        }
@@ -8489,7 +8500,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    if (mUserManager.getUserCount() > 1) {
                    if (mUserManager.getUserCount() > 1) {
                        return CODE_NONSYSTEM_USER_EXISTS;
                        return CODE_NONSYSTEM_USER_EXISTS;
                    }
                    }
                    if (AccountManager.get(mContext).getAccounts().length > 0) {
                    if (hasIncompatibleAccounts(UserHandle.USER_SYSTEM, owner)) {
                        return CODE_ACCOUNTS_NOT_EMPTY;
                        return CODE_ACCOUNTS_NOT_EMPTY;
                    }
                    }
                } else {
                } else {
@@ -8515,7 +8526,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
    }
    }


    private boolean isDeviceOwnerProvisioningAllowed(int deviceOwnerUserId) {
    private boolean isDeviceOwnerProvisioningAllowed(int deviceOwnerUserId) {
        return CODE_OK == checkSetDeviceOwnerPreCondition(deviceOwnerUserId, /* isAdb */ false);
        return CODE_OK == checkSetDeviceOwnerPreCondition(
                /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false);
    }
    }


    private boolean hasFeatureManagedUsers() {
    private boolean hasFeatureManagedUsers() {
@@ -9048,6 +9060,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            saveSettingsLocked(userHandle);
            saveSettingsLocked(userHandle);
            updateMaximumTimeToLockLocked(userHandle);
            updateMaximumTimeToLockLocked(userHandle);
            policy.mRemovingAdmins.remove(adminReceiver);
            policy.mRemovingAdmins.remove(adminReceiver);

            Slog.i(LOG_TAG, "Device admin " + adminReceiver + " removed from user " + userHandle);
        }
        }
        // The removed admin might have disabled camera, so update user
        // The removed admin might have disabled camera, so update user
        // restrictions.
        // restrictions.
@@ -9072,4 +9086,81 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            return policy.mDeviceProvisioningConfigApplied;
            return policy.mDeviceProvisioningConfigApplied;
        }
        }
    }
    }

    /**
     * Return true if a given user has any accounts that'll prevent installing a device or profile
     * owner {@code owner}.
     * - If the user has no accounts, then return false.
     * - Otherwise, if the owner is unknown (== null), or is not test-only, then return true.
     * - Otherwise, if there's any account that does not have ..._ALLOWED, or does have
     *   ..._DISALLOWED, return true.
     * - Otherwise return false.
     */
    private boolean hasIncompatibleAccounts(int userId, @Nullable ComponentName owner) {
        final long token = mInjector.binderClearCallingIdentity();
        try {
            final AccountManager am = AccountManager.get(mContext);
            final Account accounts[] = am.getAccountsAsUser(userId);
            if (accounts.length == 0) {
                return false;
            }
            final String[] feature_allow =
                    { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED };
            final String[] feature_disallow =
                    { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED };

            // Even if we find incompatible accounts along the way, we still check all accounts
            // for logging.
            boolean compatible = true;
            for (Account account : accounts) {
                if (hasAccountFeatures(am, account, feature_disallow)) {
                    Log.e(LOG_TAG, account + " has " + feature_disallow[0]);
                    compatible = false;
                }
                if (!hasAccountFeatures(am, account, feature_allow)) {
                    Log.e(LOG_TAG, account + " doesn't have " + feature_allow[0]);
                    compatible = false;
                }
            }
            if (compatible) {
                Log.w(LOG_TAG, "All accounts are compatible");
            } else {
                Log.e(LOG_TAG, "Found incompatible accounts");
            }

            // Then check if the owner is test-only.
            String log;
            if (owner == null) {
                // Owner is unknown.  Suppose it's not test-only
                compatible = false;
                log = "Only test-only device/profile owner can be installed with accounts";
            } else if (isPackageTestOnly(owner.getPackageName(), userId)) {
                if (compatible) {
                    log = "Installing test-only owner " + owner;
                } else {
                    log = "Can't install test-only owner " + owner + " with incompatible accounts";
                }
            } else {
                compatible = false;
                log = "Can't install non test-only owner " + owner + " with accounts";
            }
            if (compatible) {
                Log.w(LOG_TAG, log);
            } else {
                Log.e(LOG_TAG, log);
            }
            return !compatible;
        } finally {
            mInjector.binderRestoreCallingIdentity(token);
        }
    }

    private boolean hasAccountFeatures(AccountManager am, Account account, String[] features) {
        try {
            return am.hasFeatures(account, features, null, null).getResult();
        } catch (Exception e) {
            Log.w(LOG_TAG, "Failed to get account feature", e);
            return false;
        }
    }
}
}