diff options
| author | 2018-11-21 17:41:54 +0000 | |
|---|---|---|
| committer | 2018-12-11 21:16:31 +0000 | |
| commit | 99a66a9032666d795e731d02742441a2f7225a19 (patch) | |
| tree | a62b9284a6a50be1a0e52fef71266d72d76c8ec8 | |
| parent | c894b352d62f7cbeda37fcd66149e93383f3373e (diff) | |
Add three new delegation capabilities for profile/device owner
* DELEGATION_NETWORK_LOGGING
Allow delegated apps to control and retrieve network logging
* DELEGATION_CERT_SELECTION
Allow delegated apps to automatically select client certificates for apps.
* DELEGATION_PACKAGE_INSTALLATION
Allow delegated apps to silently install packages.
Also introduce DelegatedAdminReceiver which is analogue of the existing
DeviceAdminReceiver and enables delegated apps to receive system callbacks
related to their delegated capabilities.
This CL introduces the three new delegation scopes as well as some
implementations changes required to support these three delegations.
it also implements the actual logic around DELEGATION_NETWORK_LOGGING
and DELEGATION_CERT_SELECTION. Handling DELEGATION_PACKAGE_INSTALLATION
will be implmented in a subseqent CL.
Bug: 112982695
Test: atest com.android.cts.devicepolicy.MixedProfileOwnerTest#testDelegation
Test: atest com.android.cts.devicepolicy.MixedDeviceOwnerTest#testDelegation
Test: atest com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testDelegation
Test: Manual with TestDPC-replica
Change-Id: I508fdda0572041cf121d0e297c93d51e981545e3
6 files changed, 389 insertions, 66 deletions
diff --git a/api/current.txt b/api/current.txt index 2ef4a392ef23..c680f65e1f96 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6432,6 +6432,13 @@ package android.app.admin { field public static final android.os.Parcelable.Creator<android.app.admin.ConnectEvent> CREATOR; } + public class DelegatedAdminReceiver extends android.content.BroadcastReceiver { + ctor public DelegatedAdminReceiver(); + method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String); + method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); + method public void onReceive(android.content.Context, android.content.Intent); + } + public final class DeviceAdminInfo implements android.os.Parcelable { ctor public DeviceAdminInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public int describeContents(); @@ -6494,11 +6501,13 @@ package android.app.admin { method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle); + field public static final java.lang.String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; field public static final java.lang.String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"; field public static final java.lang.String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"; + field public static final java.lang.String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"; field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -6743,10 +6752,13 @@ package android.app.admin { field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions"; field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall"; field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install"; + field public static final java.lang.String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app"; field public static final java.lang.String DELEGATION_INSTALL_EXISTING_PACKAGE = "delegation-install-existing-package"; field public static final java.lang.String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; + field public static final java.lang.String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access"; + field public static final java.lang.String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant"; field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2 field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3 @@ -53251,7 +53263,7 @@ package android.webkit { method public abstract boolean getDomStorageEnabled(); method public abstract java.lang.String getFantasyFontFamily(); method public abstract java.lang.String getFixedFontFamily(); - method public abstract int getForceDarkMode(); + method public int getForceDarkMode(); method public abstract boolean getJavaScriptCanOpenWindowsAutomatically(); method public abstract boolean getJavaScriptEnabled(); method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm(); @@ -53298,7 +53310,7 @@ package android.webkit { method public abstract deprecated void setEnableSmoothTransition(boolean); method public abstract void setFantasyFontFamily(java.lang.String); method public abstract void setFixedFontFamily(java.lang.String); - method public abstract void setForceDarkMode(int); + method public void setForceDarkMode(int); method public abstract deprecated void setGeolocationDatabasePath(java.lang.String); method public abstract void setGeolocationEnabled(boolean); method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean); diff --git a/core/java/android/app/admin/DelegatedAdminReceiver.java b/core/java/android/app/admin/DelegatedAdminReceiver.java new file mode 100644 index 000000000000..dc8dfdff2e00 --- /dev/null +++ b/core/java/android/app/admin/DelegatedAdminReceiver.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 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 android.app.admin; + +import static android.app.admin.DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.security.KeyChain; +import android.util.Log; + +/** + * Base class for delegated apps to handle callbacks related to their delegated capabilities. + * + * <p>Delegated apps are apps that receive additional capabilities from the profile owner or + * device owner apps. Some of these capabilities involve the framework calling into the apps. + * To receive these callbacks, delegated apps should subclass this class and override the + * appropriate methods here. The subclassed receiver needs to be published in the app's + * manifest, with appropriate intent filters to mark which callbacks the receiver is interested + * in. An app can have multiple receivers as long as they listen for disjoint set of callbacks. + * For the manifest definitions, it must be protected by the + * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission to ensure only + * the system can trigger these callbacks. + * + * <p>The callback methods happen on the main thread of the process. Thus long running + * operations must be done on another thread. Note that because a receiver + * is done once returning from its receive function, such long-running operations + * should probably be done in a {@link Service}. + * + * @see DevicePolicyManager#setDelegatedScopes + * @see DeviceAdminReceiver + */ +public class DelegatedAdminReceiver extends BroadcastReceiver { + private static final String TAG = "DelegatedAdminReceiver"; + + /** + * Allows this receiver to select the alias for a private key and certificate pair for + * authentication. If this method returns null, the default {@link android.app.Activity} will + * be shown that lets the user pick a private key and certificate pair. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_CERT_SELECTION} capability. Additionally, it must + * declare an intent fitler for {@link #ACTION_CHOOSE_PRIVATE_KEY_ALIAS} in the receiver's + * manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param uid The uid asking for the private key and certificate pair. + * @param uri The URI to authenticate, may be null. + * @param alias The alias preselected by the client, or null. + * @return The private key alias to return and grant access to. + * @see KeyChain#choosePrivateKeyAlias + */ + public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri, + String alias) { + return null; + } + + /** + * Called each time a new batch of network logs can be retrieved. This callback method will only + * ever be called when network logging is enabled. The logs can only be retrieved while network + * logging is enabled. + * + * <p>If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if network logging is enabled). It will also no longer be + * possible to retrieve the network logs batch with the most recent {@code batchToken} provided + * by this callback. See {@link DevicePolicyManager#setAffiliationIds}. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_NETWORK_LOGGING} capability. Additionally, it must + * declare an intent fitler for {@link #ACTION_NETWORK_LOGS_AVAILABLE} in the receiver's + * manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param batchToken The token representing the current batch of network logs. + * @param networkLogsCount The total count of events in the current batch of network logs. + * @see DevicePolicyManager#retrieveNetworkLogs + */ + public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, + int networkLogsCount) { + } + + /** + * Intercept delegated device administrator broadcasts. Implementations should not override + * this method; implement the convenience callbacks for each action instead. + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { + int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1); + Uri uri = intent.getParcelableExtra(EXTRA_CHOOSE_PRIVATE_KEY_URI); + String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS); + String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, uri, alias); + setResultData(chosenAlias); + } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) { + long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1); + int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0); + onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount); + } else { + Log.w(TAG, "Unhandled broadcast: " + action); + } + } +} diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 1c9477d08cb3..ddbbe386452c 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -297,7 +297,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Broadcast action: notify that a new batch of network logs is ready to be collected. * @see DeviceAdminReceiver#onNetworkLogsAvailable - * @hide + * @see DelegatedAdminReceiver#onNetworkLogsAvailable */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @BroadcastBehavior(explicitOnly = true) @@ -426,7 +426,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; - /** @hide */ + /** + * Broadcast action: notify that some app is attempting to choose a KeyChain key. + * @see DeviceAdminReceiver#onChoosePrivateKeyAlias + * @see DelegatedAdminReceiver#onChoosePrivateKeyAlias + */ public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3a9728413151..35256751e44e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1485,12 +1485,46 @@ public class DevicePolicyManager { /** * Delegation of management of uninstalled packages. This scope grants access to the - * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs. + * {@link #setKeepUninstalledPackages} and {@link #getKeepUninstalledPackages} APIs. */ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; /** + * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and + * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving + * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer + * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback. + * There can be at most one app that has this delegation. + * If another app already had delegated network logging access, + * it will lose the delegation when a new app is delegated. + * + * <p> Can only be granted by Device Owner. + */ + public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; + + /** + * Grants access to selection of KeyChain certificates on behalf of requesting apps. + * Once granted the app will start receiving + * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will + * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias()}. + * There can be at most one app that has this delegation. + * If another app already had delegated certificate selection access, + * it will lose the delegation when a new app is delegated. + * + * <p> Can be granted by Device Owner or Profile Owner. + */ + public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; + + + /** + * Delegation of silent APK installation via {@link android.content.pm.PackageInstaller} APIs. + * + * <p> Can only be delegated by Device Owner. + */ + public static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; + + /** * No management for current user in-effect. This is the default. * @hide */ @@ -9148,7 +9182,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to control the network logging feature. + * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to + * control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library * functions are recorded while network logging is active: @@ -9185,16 +9220,17 @@ public class DevicePolicyManager { * all users to become affiliated. Therefore it's recommended that affiliation ids are set for * new users as soon as possible after provisioning via {@link #setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param enabled whether network logging should be enabled or not. * @throws SecurityException if {@code admin} is not a device owner. * @see #setAffiliationIds * @see #retrieveNetworkLogs */ - public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, boolean enabled) { throwIfParentInstance("setNetworkLoggingEnabled"); try { - mService.setNetworkLoggingEnabled(admin, enabled); + mService.setNetworkLoggingEnabled(admin, mContext.getPackageName(), enabled); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -9204,7 +9240,8 @@ public class DevicePolicyManager { * Return whether network logging is enabled by a device owner. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only - * be {@code null} if the caller has MANAGE_USERS permission. + * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING} + * or has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner and caller has * no MANAGE_USERS permission @@ -9212,14 +9249,15 @@ public class DevicePolicyManager { public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { throwIfParentInstance("isNetworkLoggingEnabled"); try { - return mService.isNetworkLoggingEnabled(admin); + return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Called by device owner to retrieve the most recent batch of network logging events. + * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve + * the most recent batch of network logging events. * A device owner has to provide a batchToken provided as part of * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the * token of the most recent available batch of logs, {@code null} will be returned. @@ -9238,7 +9276,8 @@ public class DevicePolicyManager { * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See * {@link DevicePolicyManager#setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param batchToken A token of the batch to retrieve * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if @@ -9248,11 +9287,11 @@ public class DevicePolicyManager { * @see #setAffiliationIds * @see DeviceAdminReceiver#onNetworkLogsAvailable */ - public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin, + public @Nullable List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, long batchToken) { throwIfParentInstance("retrieveNetworkLogs"); try { - return mService.retrieveNetworkLogs(admin, batchToken); + return mService.retrieveNetworkLogs(admin, mContext.getPackageName(), batchToken); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fcf74ee301d8..d439661c7cfa 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -366,9 +366,9 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); - void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); - boolean isNetworkLoggingEnabled(in ComponentName admin); - List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken); + void setNetworkLoggingEnabled(in ComponentName admin, in String packageName, boolean enabled); + boolean isNetworkLoggingEnabled(in ComponentName admin, in String packageName); + List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, in String packageName, long batchToken); boolean bindDeviceAdminServiceAsUser(in ComponentName admin, IApplicationThread caller, IBinder token, in Intent service, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bca3b1fe3d86..448a952000a0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -40,10 +40,13 @@ import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; +import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION; import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP; import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE; import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES; +import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; +import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; @@ -358,9 +361,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DELEGATION_PACKAGE_ACCESS, DELEGATION_PERMISSION_GRANT, DELEGATION_INSTALL_EXISTING_PACKAGE, - DELEGATION_KEEP_UNINSTALLED_PACKAGES + DELEGATION_KEEP_UNINSTALLED_PACKAGES, + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + DELEGATION_PACKAGE_INSTALLATION }; + // Subset of delegations that can only be delegated by Device Owner. + private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] { + DELEGATION_NETWORK_LOGGING, + DELEGATION_PACKAGE_INSTALLATION + }); + + // Subset of delegations that only one single package within a given user can hold + private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] { + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + }); + /** * System property whose value is either "true" or "false", indicating whether * device owner is present. @@ -5699,13 +5717,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS); - intent.setComponent(aliasChooser); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final ComponentName delegateReceiver; + delegateReceiver = resolveDelegateReceiver(DELEGATION_CERT_SELECTION, + DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS, caller.getIdentifier()); + + if (delegateReceiver != null) { + intent.setComponent(delegateReceiver); + } else { + intent.setComponent(aliasChooser); + } + final long id = mInjector.binderClearCallingIdentity(); try { mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() { @@ -5765,22 +5792,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setDelegatedScopes(ComponentName who, String delegatePackage, - List<String> scopes) throws SecurityException { + List<String> scopeList) throws SecurityException { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty"); - Preconditions.checkCollectionElementsNotNull(scopes, "Scopes"); + Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes"); // Remove possible duplicates. - scopes = new ArrayList(new ArraySet(scopes)); + final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList)); // Ensure given scopes are valid. if (scopes.retainAll(Arrays.asList(DELEGATIONS))) { throw new IllegalArgumentException("Unexpected delegation scopes"); } - + final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS); // Retrieve the user ID of the calling process. final int userId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { // Ensure calling process is device/profile owner. - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (hasDoDelegation) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } else { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + } // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N). if (shouldCheckIfDelegatePackageIsInstalled(delegatePackage, getTargetSdk(who.getPackageName(), userId), scopes)) { @@ -5793,31 +5824,57 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Set the new delegate in user policies. final DevicePolicyData policy = getUserData(userId); + List<String> exclusiveScopes = null; if (!scopes.isEmpty()) { policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes)); + exclusiveScopes = new ArrayList<>(scopes); + exclusiveScopes.retainAll(EXCLUSIVE_DELEGATIONS); } else { // Remove any delegation info if the given scopes list is empty. policy.mDelegationMap.remove(delegatePackage); } - - // Notify delegate package of updates. - final Intent intent = new Intent( - DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); - // Only call receivers registered with Context#registerReceiver (don’t wake delegate). - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - // Limit components this intent resolves to to the delegate package. - intent.setPackage(delegatePackage); - // Include the list of delegated scopes as an extra. - intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, - (ArrayList<String>) scopes); - // Send the broadcast. - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - + sendDelegationChangedBroadcast(delegatePackage, scopes, userId); + + // If set, remove exclusive scopes from all other delegates + if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) { + for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) { + final String currentPackage = entry.getKey(); + final List<String> currentScopes = entry.getValue(); + + if (!currentPackage.equals(delegatePackage)) { + // Iterate through all other delegates + if (currentScopes.removeAll(exclusiveScopes)) { + // And if this delegate had some exclusive scopes which are now moved + // to the new delegate, notify about its delegation changes. + if (currentScopes.isEmpty()) { + policy.mDelegationMap.remove(currentPackage); + } + sendDelegationChangedBroadcast(currentPackage, + new ArrayList<>(currentScopes), userId); + } + } + } + } // Persist updates. saveSettingsLocked(userId); } } + private void sendDelegationChangedBroadcast(String delegatePackage, ArrayList<String> scopes, + int userId) { + // Notify delegate package of updates. + final Intent intent = new Intent( + DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); + // Only call receivers registered with Context#registerReceiver (don’t wake delegate). + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + // Limit components this intent resolves to to the delegate package. + intent.setPackage(delegatePackage); + // Include the list of delegated scopes as an extra. + intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes); + // Send the broadcast. + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + /** * Get the delegation scopes given to a delegate package by a device owner or profile owner. * @@ -5885,17 +5942,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { // Ensure calling process is device/profile owner. getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - final DevicePolicyData policy = getUserData(userId); + return getDelegatePackagesInternalLocked(scope, userId); + } + } - // Create a list to hold the resulting delegate packages. - final List<String> delegatePackagesWithScope = new ArrayList<>(); - // Add all delegations containing scope to the result list. - for (int i = 0; i < policy.mDelegationMap.size(); i++) { - if (policy.mDelegationMap.valueAt(i).contains(scope)) { - delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); - } + private List<String> getDelegatePackagesInternalLocked(String scope, int userId) { + final DevicePolicyData policy = getUserData(userId); + + // Create a list to hold the resulting delegate packages. + final List<String> delegatePackagesWithScope = new ArrayList<>(); + // Add all delegations containing scope to the result list. + for (int i = 0; i < policy.mDelegationMap.size(); i++) { + if (policy.mDelegationMap.valueAt(i).contains(scope)) { + delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); + } + } + return delegatePackagesWithScope; + } + + /** + * Return the ComponentName of the receiver that handles the given broadcast action, from + * the app that holds the given delegation capability. If the app defines multiple receivers + * with the same intent action filter, will return any one of them nondeterministically. + * + * @return ComponentName of the receiver or {@null} if none exists. + */ + private ComponentName resolveDelegateReceiver(String scope, String action, int userId) { + + final List<String> delegates; + synchronized (getLockObject()) { + delegates = getDelegatePackagesInternalLocked(scope, userId); + } + if (delegates.size() != 1) { + Slog.wtf(LOG_TAG, "More than one delegate holds " + scope); + return null; + } + final String pkg = delegates.get(0); + Intent intent = new Intent(action); + intent.setPackage(pkg); + final List<ResolveInfo> receivers; + try { + receivers = mIPackageManager.queryIntentReceivers( + intent, null, 0, userId).getList(); + } catch (RemoteException e) { + return null; + } + final int count = receivers.size(); + if (count >= 1) { + if (count > 1) { + Slog.w(LOG_TAG, pkg + " defines more than one delegate receiver for " + action); } - return delegatePackagesWithScope; + return receivers.get(0).activityInfo.getComponentName(); + } else { + return null; } } @@ -5958,15 +6057,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy, String scope) { + enforceCanManageScopeOrCheckPermission(who, callerPackage, reqPolicy, scope, null); + } + + /** + * Throw a security exception if a ComponentName is given and it is not a device/profile owner + * OR if the calling process is not a delegate of the given scope and does not hold the + * required permission. + */ + private void enforceCanManageScopeOrCheckPermission(@Nullable ComponentName who, + @NonNull String callerPackage, int reqPolicy, @NonNull String scope, + @Nullable String permission) { // If a ComponentName is given ensure it is a device or profile owner according to policy. if (who != null) { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, reqPolicy); } - // If no ComponentName is given ensure calling process has scope delegation. - } else if (!isCallerDelegate(callerPackage, scope)) { - throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() - + " is not a delegate of scope " + scope + "."); + } else { + // If no ComponentName is given ensure calling process has scope delegation or required + // permission + if (isCallerDelegate(callerPackage, scope)) { + return; + } + if (permission == null) { + throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + + " is not a delegate of scope " + scope + "."); + } else { + mContext.enforceCallingOrSelfPermission(permission, null); + } } } @@ -6900,9 +7018,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException { + private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) + throws SecurityException { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + ensureAllUsersAffiliated(); + } + + private void ensureAllUsersAffiliated() throws SecurityException { + synchronized (getLockObject()) { if (!areAllUsersAffiliatedWithDeviceLocked()) { throw new SecurityException("Not all users are affiliated."); } @@ -6961,14 +7086,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } void sendDeviceOwnerCommand(String action, Bundle extras) { - int deviceOwnerUserId; - ComponentName deviceOwnerComponent; + final int deviceOwnerUserId; synchronized (getLockObject()) { deviceOwnerUserId = mOwners.getDeviceOwnerUserId(); - deviceOwnerComponent = mOwners.getDeviceOwnerComponent(); } - sendActiveAdminCommand(action, extras, deviceOwnerUserId, - deviceOwnerComponent); + + ComponentName receiverComponent = null; + if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) { + receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, + deviceOwnerUserId); + } + if (receiverComponent == null) { + synchronized (getLockObject()) { + receiverComponent = mOwners.getDeviceOwnerComponent(); + } + } + sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) { @@ -12381,13 +12514,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName, boolean enabled) { if (!mHasFeature) { return; } synchronized (getLockObject()) { - Preconditions.checkNotNull(admin); - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); if (enabled == isNetworkLoggingEnabledInternalLocked()) { // already in the requested state @@ -12488,12 +12622,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isNetworkLoggingEnabled(ComponentName admin) { + public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName) { if (!mHasFeature) { return false; } synchronized (getLockObject()) { - enforceDeviceOwnerOrManageUsers(); + enforceCanManageScopeOrCheckPermission(admin, packageName, + DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, DELEGATION_NETWORK_LOGGING, + android.Manifest.permission.MANAGE_USERS); return isNetworkLoggingEnabledInternalLocked(); } } @@ -12511,12 +12648,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH */ @Override - public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) { + public List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, + @NonNull String packageName, long batchToken) { if (!mHasFeature) { return null; } - Preconditions.checkNotNull(admin); - ensureDeviceOwnerAndAllUsersAffiliated(admin); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); + ensureAllUsersAffiliated(); synchronized (getLockObject()) { if (mNetworkLogger == null |