MonitoringCertTask no longer relies on software.device_admin
Added a test to validate that it still works the way it should before and after the change. Bug: 33258404 Bug: 35196414 Fix: 35129745 Test: runtest -x services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java Test: also manual, instructions: Test: (1) Disable software.device_admin from tablet_core_hardware, rebuild. Test: (2) Install CA cert. Notification should appear. Test: (3) Reboot. Notification should still be there. Change-Id: Id992725c1844a2fffbde4d8acaba531e99f853ad
This commit is contained in:
@@ -588,7 +588,7 @@ public final class KeyChain {
|
||||
* @hide for reuse by CertInstaller and Settings.
|
||||
* @see KeyChain#bind
|
||||
*/
|
||||
public final static class KeyChainConnection implements Closeable {
|
||||
public static class KeyChainConnection implements Closeable {
|
||||
private final Context context;
|
||||
private final ServiceConnection serviceConnection;
|
||||
private final IKeyChainService service;
|
||||
|
||||
@@ -99,6 +99,7 @@ import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
@@ -208,7 +209,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
*/
|
||||
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
|
||||
private static final String LOG_TAG = "DevicePolicyManager";
|
||||
protected static final String LOG_TAG = "DevicePolicyManager";
|
||||
|
||||
private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
|
||||
|
||||
@@ -252,7 +253,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION
|
||||
= "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
|
||||
|
||||
private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
|
||||
private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
|
||||
private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002;
|
||||
|
||||
@@ -409,6 +409,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
}
|
||||
};
|
||||
|
||||
/** Listens only if mHasFeature == true. */
|
||||
private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@@ -420,6 +421,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
}
|
||||
};
|
||||
|
||||
/** Listens only if mHasFeature == true. */
|
||||
private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@@ -513,7 +515,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
|
||||
final Handler mHandler;
|
||||
|
||||
BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
/** Listens on any device, even when mHasFeature == false. */
|
||||
final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (StorageManager.inCryptKeeperBounce()) {
|
||||
return;
|
||||
}
|
||||
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
|
||||
new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
|
||||
.execute(userHandle);
|
||||
}
|
||||
};
|
||||
|
||||
/** Listens only if mHasFeature == true. */
|
||||
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
@@ -559,14 +575,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Intent.ACTION_USER_UNLOCKED.equals(action)
|
||||
|| Intent.ACTION_USER_STARTED.equals(action)
|
||||
|| KeyChain.ACTION_TRUST_STORE_CHANGED.equals(action)) {
|
||||
if (!StorageManager.inCryptKeeperBounce()) {
|
||||
new MonitoringCertNotificationTask().execute(
|
||||
intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL));
|
||||
}
|
||||
}
|
||||
|
||||
if (Intent.ACTION_USER_ADDED.equals(action)) {
|
||||
sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
|
||||
synchronized (DevicePolicyManagerService.this) {
|
||||
@@ -1490,6 +1499,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
|
||||
final String packageName = mContext.getPackageName();
|
||||
return mContext.createPackageContextAsUser(packageName, 0, user);
|
||||
}
|
||||
|
||||
Resources getResources() {
|
||||
return mContext.getResources();
|
||||
}
|
||||
|
||||
Owners newOwners() {
|
||||
return new Owners(getUserManager(), getUserManagerInternal(),
|
||||
getPackageManagerInternal());
|
||||
@@ -1725,6 +1743,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
boolean securityLogIsLoggingEnabled() {
|
||||
return SecurityLog.isLoggingEnabled();
|
||||
}
|
||||
|
||||
KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException {
|
||||
return KeyChain.bindAsUser(mContext, user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1755,18 +1777,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
|
||||
mIsWatch = mInjector.getPackageManager()
|
||||
.hasSystemFeature(PackageManager.FEATURE_WATCH);
|
||||
|
||||
// Broadcast filter for changes to the trusted certificate store. These changes get a
|
||||
// separate intent filter so we can listen to them even when device_admin is off.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_USER_STARTED);
|
||||
filter.addAction(Intent.ACTION_USER_UNLOCKED);
|
||||
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
|
||||
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
|
||||
mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
|
||||
|
||||
if (!mHasFeature) {
|
||||
// Skip the rest of the initialization
|
||||
return;
|
||||
}
|
||||
IntentFilter filter = new IntentFilter();
|
||||
|
||||
filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
|
||||
filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
|
||||
filter.addAction(Intent.ACTION_USER_ADDED);
|
||||
filter.addAction(Intent.ACTION_USER_REMOVED);
|
||||
filter.addAction(Intent.ACTION_USER_STARTED);
|
||||
filter.addAction(Intent.ACTION_USER_UNLOCKED);
|
||||
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
|
||||
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
|
||||
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
|
||||
filter = new IntentFilter();
|
||||
@@ -2946,125 +2977,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(Integer... params) {
|
||||
int userHandle = params[0];
|
||||
/**
|
||||
* Remove deleted CA certificates from the "approved" list for a particular user, counting
|
||||
* the number still remaining to approve.
|
||||
*
|
||||
* @param userHandle user to check for. This must be a real user and not, for example,
|
||||
* {@link UserHandle#ALL}.
|
||||
* @param installedCertificates the full set of certificate authorities currently installed for
|
||||
* {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
|
||||
* correspond to some subset of this.
|
||||
*
|
||||
* @return number of certificates yet to be approved by {@param userHandle}.
|
||||
*/
|
||||
protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
|
||||
final @NonNull Collection<String> installedCertificates) {
|
||||
enforceManageUsers();
|
||||
|
||||
if (userHandle == UserHandle.USER_ALL) {
|
||||
for (UserInfo userInfo : mUserManager.getUsers(true)) {
|
||||
manageNotification(userInfo.getUserHandle());
|
||||
}
|
||||
} else {
|
||||
manageNotification(UserHandle.of(userHandle));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!mHasFeature) {
|
||||
return installedCertificates.size();
|
||||
} else {
|
||||
final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
|
||||
|
||||
private void manageNotification(UserHandle userHandle) {
|
||||
if (!mUserManager.isUserUnlocked(userHandle)) {
|
||||
return;
|
||||
// Remove deleted certificates. Flush xml if necessary.
|
||||
if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
|
||||
saveSettingsLocked(userHandle.getIdentifier());
|
||||
}
|
||||
|
||||
// Call out to KeyChain to check for CAs which are waiting for approval.
|
||||
final List<String> pendingCertificates;
|
||||
try {
|
||||
pendingCertificates = getInstalledCaCertificates(userHandle);
|
||||
} catch (RemoteException | RuntimeException e) {
|
||||
Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (DevicePolicyManagerService.this) {
|
||||
final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
|
||||
|
||||
// Remove deleted certificates. Flush xml if necessary.
|
||||
if (policy.mAcceptedCaCertificates.retainAll(pendingCertificates)) {
|
||||
saveSettingsLocked(userHandle.getIdentifier());
|
||||
}
|
||||
// Trim to approved certificates.
|
||||
pendingCertificates.removeAll(policy.mAcceptedCaCertificates);
|
||||
}
|
||||
|
||||
if (pendingCertificates.isEmpty()) {
|
||||
mInjector.getNotificationManager().cancelAsUser(
|
||||
null, MONITORING_CERT_NOTIFICATION_ID, userHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build and show a warning notification
|
||||
int smallIconId;
|
||||
String contentText;
|
||||
int parentUserId = userHandle.getIdentifier();
|
||||
if (getProfileOwner(userHandle.getIdentifier()) != null) {
|
||||
contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
|
||||
getProfileOwnerName(userHandle.getIdentifier()));
|
||||
smallIconId = R.drawable.stat_sys_certificate_info;
|
||||
parentUserId = getProfileParentId(userHandle.getIdentifier());
|
||||
} else if (getDeviceOwnerUserId() == userHandle.getIdentifier()) {
|
||||
contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
|
||||
getDeviceOwnerName());
|
||||
smallIconId = R.drawable.stat_sys_certificate_info;
|
||||
} else {
|
||||
contentText = mContext.getString(R.string.ssl_ca_cert_noti_by_unknown);
|
||||
smallIconId = android.R.drawable.stat_sys_warning;
|
||||
}
|
||||
|
||||
final int numberOfCertificates = pendingCertificates.size();
|
||||
Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
|
||||
dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
dialogIntent.setPackage("com.android.settings");
|
||||
dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, numberOfCertificates);
|
||||
dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
|
||||
PendingIntent notifyIntent = PendingIntent.getActivityAsUser(mContext, 0,
|
||||
dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
|
||||
new UserHandle(parentUserId));
|
||||
|
||||
final Context userContext;
|
||||
try {
|
||||
final String packageName = mContext.getPackageName();
|
||||
userContext = mContext.createPackageContextAsUser(packageName, 0, userHandle);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
|
||||
return;
|
||||
}
|
||||
final Notification noti = new Notification.Builder(userContext)
|
||||
.setSmallIcon(smallIconId)
|
||||
.setContentTitle(mContext.getResources().getQuantityText(
|
||||
R.plurals.ssl_ca_cert_warning, numberOfCertificates))
|
||||
.setContentText(contentText)
|
||||
.setContentIntent(notifyIntent)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setShowWhen(false)
|
||||
.setColor(mContext.getColor(
|
||||
com.android.internal.R.color.system_notification_accent_color))
|
||||
.build();
|
||||
|
||||
mInjector.getNotificationManager().notifyAsUser(
|
||||
null, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
|
||||
}
|
||||
|
||||
private List<String> getInstalledCaCertificates(UserHandle userHandle)
|
||||
throws RemoteException, RuntimeException {
|
||||
KeyChainConnection conn = null;
|
||||
try {
|
||||
conn = KeyChain.bindAsUser(mContext, userHandle);
|
||||
List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
|
||||
List<String> result = new ArrayList<>(aliases.size());
|
||||
for (int i = 0; i < aliases.size(); i++) {
|
||||
result.add(aliases.get(i).string);
|
||||
}
|
||||
return result;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (AssertionError e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
// Trim approved certificates from the count.
|
||||
return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4629,7 +4569,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
}
|
||||
saveSettingsLocked(userId);
|
||||
}
|
||||
new MonitoringCertNotificationTask().execute(userId);
|
||||
new MonitoringCertNotificationTask(this, mInjector).execute(userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4653,7 +4593,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
saveSettingsLocked(userInfo.id);
|
||||
}
|
||||
|
||||
new MonitoringCertNotificationTask().execute(userInfo.id);
|
||||
new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7148,7 +7088,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
|
||||
}
|
||||
|
||||
private int getProfileParentId(int userHandle) {
|
||||
protected int getProfileParentId(int userHandle) {
|
||||
final long ident = mInjector.binderClearCallingIdentity();
|
||||
try {
|
||||
UserInfo parentUser = mUserManager.getProfileParent(userHandle);
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.devicepolicy;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.security.KeyChain.KeyChainConnection;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.ParcelableString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
|
||||
protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
|
||||
protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
|
||||
|
||||
private final DevicePolicyManagerService mService;
|
||||
private final DevicePolicyManagerService.Injector mInjector;
|
||||
|
||||
public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
|
||||
final DevicePolicyManagerService.Injector injector) {
|
||||
super();
|
||||
mService = service;
|
||||
mInjector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Integer... params) {
|
||||
int userHandle = params[0];
|
||||
|
||||
if (userHandle == UserHandle.USER_ALL) {
|
||||
for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
|
||||
repostOrClearNotification(userInfo.getUserHandle());
|
||||
}
|
||||
} else {
|
||||
repostOrClearNotification(UserHandle.of(userHandle));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void repostOrClearNotification(UserHandle userHandle) {
|
||||
if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call out to KeyChain to check for CAs which are waiting for approval.
|
||||
final int pendingCertificateCount;
|
||||
try {
|
||||
pendingCertificateCount = mService.retainAcceptedCertificates(
|
||||
userHandle, getInstalledCaCertificates(userHandle));
|
||||
} catch (RemoteException | RuntimeException e) {
|
||||
Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingCertificateCount != 0) {
|
||||
showNotification(userHandle, pendingCertificateCount);
|
||||
} else {
|
||||
mInjector.getNotificationManager().cancelAsUser(
|
||||
LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
|
||||
// Create a context for the target user.
|
||||
final Context userContext;
|
||||
try {
|
||||
userContext = mInjector.createContextAsUser(userHandle);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build and show a warning notification
|
||||
int smallIconId;
|
||||
String contentText;
|
||||
int parentUserId = userHandle.getIdentifier();
|
||||
Resources resources = mInjector.getResources();
|
||||
if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
|
||||
contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
|
||||
mService.getProfileOwnerName(userHandle.getIdentifier()));
|
||||
smallIconId = R.drawable.stat_sys_certificate_info;
|
||||
parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
|
||||
} else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
|
||||
contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
|
||||
mService.getDeviceOwnerName());
|
||||
smallIconId = R.drawable.stat_sys_certificate_info;
|
||||
} else {
|
||||
contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
|
||||
smallIconId = android.R.drawable.stat_sys_warning;
|
||||
}
|
||||
|
||||
Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
|
||||
dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
// TODO this next line is taken from original notification code in
|
||||
// {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
|
||||
dialogIntent.setPackage("com.android.settings");
|
||||
dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
|
||||
dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
|
||||
PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
|
||||
dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
|
||||
UserHandle.of(parentUserId));
|
||||
|
||||
final Notification noti = new Notification.Builder(userContext)
|
||||
.setSmallIcon(smallIconId)
|
||||
.setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
|
||||
pendingCertificateCount))
|
||||
.setContentText(contentText)
|
||||
.setContentIntent(notifyIntent)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setShowWhen(false)
|
||||
.setColor(R.color.system_notification_accent_color)
|
||||
.build();
|
||||
|
||||
mInjector.getNotificationManager().notifyAsUser(
|
||||
LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
|
||||
}
|
||||
|
||||
private List<String> getInstalledCaCertificates(UserHandle userHandle)
|
||||
throws RemoteException, RuntimeException {
|
||||
try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
|
||||
List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
|
||||
List<String> result = new ArrayList<>(aliases.size());
|
||||
for (int i = 0; i < aliases.size(); i++) {
|
||||
result.add(aliases.get(i).string);
|
||||
}
|
||||
return result;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (AssertionError e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import android.os.PowerManagerInternal;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.UserManagerInternal;
|
||||
import android.security.KeyChain;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
@@ -375,5 +376,10 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
|
||||
boolean isBuildDebuggable() {
|
||||
return context.buildMock.isDebuggable;
|
||||
}
|
||||
|
||||
@Override
|
||||
KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
|
||||
return context.keyChainConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.admin.DeviceAdminReceiver;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
@@ -32,6 +34,7 @@ import android.content.ServiceConnection;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.IIpConnectivityMetrics;
|
||||
@@ -52,10 +55,13 @@ import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.ParcelableString;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.pm.UserRestrictionsUtils;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@@ -76,13 +82,16 @@ import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isNull;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
@@ -1193,6 +1202,53 @@ public class DevicePolicyManagerTest extends DpmTestBase {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void testCertificateDisclosure() throws Exception {
|
||||
final int userId = DpmMockContext.CALLER_USER_HANDLE;
|
||||
final UserHandle user = UserHandle.of(userId);
|
||||
|
||||
mContext.applicationInfo = new ApplicationInfo();
|
||||
mContext.callerPermissions.add(permission.MANAGE_USERS);
|
||||
mContext.packageName = "com.android.frameworks.servicestests";
|
||||
mContext.userContexts.put(user, mContext);
|
||||
when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
|
||||
|
||||
ParceledListSlice<ParcelableString> oneCert = asSlice(new String[] {"1"});
|
||||
ParceledListSlice<ParcelableString> fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
|
||||
|
||||
final String TEST_STRING = "Test for exactly 2 certs out of 4";
|
||||
doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
|
||||
|
||||
// Given that we have exactly one certificate installed,
|
||||
when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
|
||||
// when that certificate is approved,
|
||||
dpms.approveCaCert(oneCert.getList().get(0).string, userId, true);
|
||||
// a notification should not be shown.
|
||||
verify(mContext.notificationManager, timeout(1000))
|
||||
.cancelAsUser(anyString(), anyInt(), eq(user));
|
||||
|
||||
// Given that we have four certificates installed,
|
||||
when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
|
||||
// when two of them are approved (one of them approved twice hence no action),
|
||||
dpms.approveCaCert(fourCerts.getList().get(0).string, userId, true);
|
||||
dpms.approveCaCert(fourCerts.getList().get(1).string, userId, true);
|
||||
// a notification should be shown saying that there are two certificates left to approve.
|
||||
verify(mContext.notificationManager, timeout(1000))
|
||||
.notifyAsUser(anyString(), anyInt(), argThat(
|
||||
new BaseMatcher<Notification>() {
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
final Notification noti = (Notification) item;
|
||||
return TEST_STRING.equals(
|
||||
noti.extras.getString(Notification.EXTRA_TITLE));
|
||||
}
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(
|
||||
"Notification{title=\"" + TEST_STRING + "\"}");
|
||||
}
|
||||
}), eq(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test for delegate set/get and general delegation. Tests verifying that delegated
|
||||
* privileges can acually be exercised by a delegate are not covered here.
|
||||
@@ -3734,4 +3790,20 @@ public class DevicePolicyManagerTest extends DpmTestBase {
|
||||
assertTrue(dpm.setProfileOwner(admin, null, userId));
|
||||
mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert String[] to ParceledListSlice<ParcelableString>.
|
||||
* <p>
|
||||
* TODO: This shouldn't be necessary. If ParcelableString does need to exist, it also needs
|
||||
* a real constructor.
|
||||
*/
|
||||
private static ParceledListSlice<ParcelableString> asSlice(String[] s) {
|
||||
List<ParcelableString> list = new ArrayList<>(s.length);
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
ParcelableString item = new ParcelableString();
|
||||
item.string = s[i];
|
||||
list.add(i, item);
|
||||
}
|
||||
return new ParceledListSlice<ParcelableString>(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,11 @@ import android.os.PowerManagerInternal;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.UserManagerInternal;
|
||||
import android.security.KeyChain;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.test.mock.MockContext;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.IWindowManager;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
@@ -58,10 +60,12 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.Matchers.anyBoolean;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -290,6 +294,7 @@ public class DpmMockContext extends MockContext {
|
||||
public final TelephonyManager telephonyManager;
|
||||
public final AccountManager accountManager;
|
||||
public final AlarmManager alarmManager;
|
||||
public final KeyChain.KeyChainConnection keyChainConnection;
|
||||
|
||||
/** Note this is a partial mock, not a real mock. */
|
||||
public final PackageManager packageManager;
|
||||
@@ -300,6 +305,9 @@ public class DpmMockContext extends MockContext {
|
||||
|
||||
public final BuildMock buildMock = new BuildMock();
|
||||
|
||||
/** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
|
||||
public final Map<UserHandle, Context> userContexts = new ArrayMap<>();
|
||||
|
||||
public String packageName = null;
|
||||
|
||||
public ApplicationInfo applicationInfo = null;
|
||||
@@ -335,6 +343,7 @@ public class DpmMockContext extends MockContext {
|
||||
telephonyManager = mock(TelephonyManager.class);
|
||||
accountManager = mock(AccountManager.class);
|
||||
alarmManager = mock(AlarmManager.class);
|
||||
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
|
||||
|
||||
// Package manager is huge, so we use a partial mock instead.
|
||||
packageManager = spy(context.getPackageManager());
|
||||
@@ -689,6 +698,19 @@ public class DpmMockContext extends MockContext {
|
||||
spiedContext.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
if (!userContexts.containsKey(user)) {
|
||||
return super.createPackageContextAsUser(packageName, flags, user);
|
||||
}
|
||||
if (!getPackageName().equals(packageName)) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Creating a context as another package is not implemented");
|
||||
}
|
||||
return userContexts.get(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentResolver getContentResolver() {
|
||||
return contentResolver;
|
||||
|
||||
Reference in New Issue
Block a user