blob: e6c5791ecb7c313546c13a3d8f8bdac626e6697d [file] [log] [blame]
/*
* 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.cts.deviceandprofileowner;
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_NETWORK_LOGGING;
import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Process;
import android.os.UserManager;
import android.test.MoreAsserts;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Test that an app granted delegation scopes via {@link DevicePolicyManager#setDelegatedScopes} is
* notified of its new scopes by a broadcast.
*/
public class DelegationTest extends BaseDeviceAdminTest {
private static final String TAG = "DelegationTest";
private static final String DELEGATE_PKG = "com.android.cts.delegate";
private static final String DELEGATE_ACTIVITY_NAME =
DELEGATE_PKG + ".DelegatedScopesReceiverActivity";
private static final String DELEGATE_SERVICE_NAME =
DELEGATE_PKG + ".DelegatedScopesReceiverService";
private static final String TEST_PKG = "com.android.cts.apprestrictions.targetapp";
// Broadcasts received from the delegate app.
private static final String ACTION_REPORT_SCOPES = "com.android.cts.delegate.report_scopes";
private static final String ACTION_RUNNING = "com.android.cts.delegate.running";
// Semaphores to synchronize communication with delegate app.
private volatile String[] mReceivedScopes;
private Semaphore mReceivedScopeReportSemaphore;
private Semaphore mReceivedRunningSemaphore;
// Receiver for incoming broadcasts from the delegate app.
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "onReceive(): " + intent.getAction() + " on user " + Process.myUserHandle());
if (ACTION_REPORT_SCOPES.equals(intent.getAction())) {
synchronized (DelegationTest.this) {
mReceivedScopes = intent.getStringArrayExtra(EXTRA_DELEGATION_SCOPES);
mReceivedScopeReportSemaphore.release();
}
} else if (ACTION_RUNNING.equals(intent.getAction())) {
synchronized (DelegationTest.this) {
mReceivedRunningSemaphore.release();
}
}
}
};
@Override
public void setUp() throws Exception {
super.setUp();
mReceivedScopeReportSemaphore = new Semaphore(0);
mReceivedRunningSemaphore = new Semaphore(0);
mReceivedScopes = null;
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_REPORT_SCOPES);
filter.addAction(ACTION_RUNNING);
mContext.registerReceiver(mReceiver, filter);
}
@Override
public void tearDown() throws Exception {
mContext.unregisterReceiver(mReceiver);
setDelegatedScopes(TEST_PKG, Collections.emptyList());
setDelegatedScopes(DELEGATE_PKG, Collections.emptyList());
super.tearDown();
}
public void testDelegateReceivesScopeChangedBroadcast() throws InterruptedException {
if (UserManager.isHeadlessSystemUserMode()) {
// TODO(b/190627898): this test launched an activity to receive the broadcast from DPM,
// but headless system user cannot launch activity. To make things worse, the intent
// is only sent to registered receivers, so we cannot use the existing receivers from
// DpmWrapper, we would need to start a service on user 0 to receive the broadcast,
// which would require a lot of changes:
// - calling APIs / Shell commands to allow an app in the bg to start a service
// - add a "launchIntent()" method on DpmWrapper so the intent is launched by user 0
//
// It might not be worth to make these changes, but rather wait for the test refactoring
Log.i(TAG, "Skipping testDelegateReceivesScopeChangedBroadcast() on headless system "
+ "user mode");
return;
}
// Prepare the scopes to be delegated.
final List<String> scopes = Arrays.asList(
DELEGATION_CERT_INSTALL,
DELEGATION_APP_RESTRICTIONS,
DELEGATION_BLOCK_UNINSTALL,
DELEGATION_ENABLE_SYSTEM_APP);
// Start delegate so it can receive the scopes changed broadcast from DevicePolicyManager.
startAndWaitDelegateActivity();
// Set the delegated scopes.
setDelegatedScopes(DELEGATE_PKG, scopes);
// Wait until the delegate reports its new scopes.
String reportedScopes[] = waitReportedScopes();
// Check that the reported scopes correspond to scopes we delegated.
assertNotNull("Received null scopes from delegate", reportedScopes);
MoreAsserts.assertContentsInAnyOrder("Delegated scopes do not match broadcasted scopes",
scopes, reportedScopes);
}
public void testCantDelegateToUninstalledPackage() {
// Prepare the package name and scopes to be delegated.
final String NON_EXISTENT_PKG = "com.android.nonexistent.delegate";
final List<String> scopes = Arrays.asList(
DELEGATION_CERT_INSTALL,
DELEGATION_ENABLE_SYSTEM_APP);
try {
// Trying to delegate to non existent package should throw.
setDelegatedScopes(NON_EXISTENT_PKG, scopes);
fail("Should throw when delegating to non existent package");
} catch(IllegalArgumentException expected) {
}
// Assert no scopes were delegated.
assertTrue("Delegation scopes granted to non existent package", mDevicePolicyManager
.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, NON_EXISTENT_PKG).isEmpty());
}
public void testCanRetrieveDelegates() {
final List<String> someScopes = Arrays.asList(
DELEGATION_APP_RESTRICTIONS,
DELEGATION_ENABLE_SYSTEM_APP);
final List<String> otherScopes = Arrays.asList(
DELEGATION_BLOCK_UNINSTALL,
DELEGATION_ENABLE_SYSTEM_APP);
// In the beginning there are no delegates.
assertTrue("No delegates should be found", getDelegatePackages(DELEGATION_APP_RESTRICTIONS)
.isEmpty());
assertTrue("No delegates should be found", getDelegatePackages(DELEGATION_BLOCK_UNINSTALL)
.isEmpty());
assertTrue("No delegates should be found", getDelegatePackages(DELEGATION_ENABLE_SYSTEM_APP)
.isEmpty());
// After delegating scopes to two packages.
setDelegatedScopes(DELEGATE_PKG, someScopes);
setDelegatedScopes(TEST_PKG, otherScopes);
// The expected delegates are returned.
assertTrue("Expected delegate not found", getDelegatePackages(DELEGATION_APP_RESTRICTIONS)
.contains(DELEGATE_PKG));
assertTrue("Expected delegate not found", getDelegatePackages(DELEGATION_BLOCK_UNINSTALL)
.contains(TEST_PKG));
assertTrue("Expected delegate not found", getDelegatePackages(DELEGATION_ENABLE_SYSTEM_APP)
.contains(DELEGATE_PKG));
assertTrue("Expected delegate not found", getDelegatePackages(DELEGATION_ENABLE_SYSTEM_APP)
.contains(TEST_PKG));
// Packages are only returned in their recpective scopes.
assertFalse("Unexpected delegate package", getDelegatePackages(DELEGATION_APP_RESTRICTIONS)
.contains(TEST_PKG));
assertFalse("Unexpected delegate package", getDelegatePackages(DELEGATION_BLOCK_UNINSTALL)
.contains(DELEGATE_PKG));
assertFalse("Unexpected delegate package", getDelegatePackages(DELEGATION_CERT_INSTALL)
.contains(DELEGATE_PKG));
assertFalse("Unexpected delegate package", getDelegatePackages(DELEGATION_CERT_INSTALL)
.contains(TEST_PKG));
}
public void testDeviceOwnerOrManagedPoOnlyDelegations() throws Exception {
final String [] doOrManagedPoDelegations = { DELEGATION_NETWORK_LOGGING };
final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
mContext.getPackageName());
final boolean isManagedProfileOwner = mDevicePolicyManager.getProfileOwner() != null
&& mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT);
for (String scope : doOrManagedPoDelegations) {
if (isDeviceOwner || isManagedProfileOwner) {
try {
setDelegatedScopes(DELEGATE_PKG, Collections.singletonList(scope));
} catch (SecurityException e) {
fail("DO or managed PO fails to delegate " + scope + " exception: " + e);
Log.e(TAG, "DO or managed PO fails to delegate " + scope, e);
}
} else {
assertThrows("PO not in a managed profile shouldn't be able to delegate " + scope,
SecurityException.class,
() -> setDelegatedScopes(DELEGATE_PKG, Collections.singletonList(scope)));
}
}
}
public void testDeviceOwnerOrOrgOwnedManagedPoOnlyDelegations() throws Exception {
final String [] doOrOrgOwnedManagedPoDelegations = { DELEGATION_SECURITY_LOGGING };
final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
mContext.getPackageName());
final boolean isOrgOwnedManagedProfileOwner = mDevicePolicyManager.getProfileOwner() != null
&& mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT)
&& mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
for (String scope : doOrOrgOwnedManagedPoDelegations) {
if (isDeviceOwner || isOrgOwnedManagedProfileOwner) {
try {
setDelegatedScopes(DELEGATE_PKG, Collections.singletonList(scope));
} catch (SecurityException e) {
fail("DO or organization-owned managed PO fails to delegate " + scope
+ " exception: " + e);
Log.e(TAG, "DO or organization-owned managed PO fails to delegate " + scope, e);
}
} else {
assertThrows("PO not in an organization-owned managed profile shouldn't be able to "
+ "delegate " + scope,
SecurityException.class,
() -> setDelegatedScopes(DELEGATE_PKG, Collections.singletonList(scope)));
}
}
}
public void testExclusiveDelegations() throws Exception {
final List<String> exclusiveDelegations = new ArrayList<>(Arrays.asList(
DELEGATION_CERT_SELECTION));
if (mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName())) {
exclusiveDelegations.add(DELEGATION_NETWORK_LOGGING);
exclusiveDelegations.add(DELEGATION_SECURITY_LOGGING);
}
for (String scope : exclusiveDelegations) {
testExclusiveDelegation(scope);
}
}
private void testExclusiveDelegation(String scope) throws Exception {
setDelegatedScopes(DELEGATE_PKG, Collections.singletonList(scope));
// Set exclusive scope on TEST_PKG should lead to the scope being removed from the
// previous delegate DELEGATE_PKG
setDelegatedScopes(TEST_PKG, Collections.singletonList(scope));
assertThat(mDevicePolicyManager.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, TEST_PKG))
.containsExactly(scope);
assertThat(mDevicePolicyManager.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_PKG))
.isEmpty();
}
private List<String> getDelegatePackages(String scope) {
List<String> packages = mDevicePolicyManager.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
scope);
Log.v(TAG, "getDelegatePackages(" + scope + "): " + packages);
return packages;
}
private void startAndWaitDelegateActivity() throws InterruptedException {
ComponentName componentName = new ComponentName(DELEGATE_PKG, DELEGATE_ACTIVITY_NAME);
Log.d(TAG, "Starting " + componentName + " on user " + Process.myUserHandle());
mContext.startActivity(new Intent()
.setComponent(componentName)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
assertTrue("DelegateApp did not start in time.",
mReceivedRunningSemaphore.tryAcquire(10, TimeUnit.SECONDS));
}
private String[] waitReportedScopes() throws InterruptedException {
assertTrue("DelegateApp did not report scope in time.",
mReceivedScopeReportSemaphore.tryAcquire(10, TimeUnit.SECONDS));
return mReceivedScopes;
}
}