Merge "Allow DO/PO to be installed with certain preconfigured accounts." into nyc-mr1-dev

This commit is contained in:
Makoto Onuki
2016-09-07 21:36:35 +00:00
committed by Android (Google) Code Review
2 changed files with 143 additions and 25 deletions

View File

@@ -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

View File

@@ -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;
}
}
} }