Merge "Check consents for content protection flow" into udc-qpr-dev

This commit is contained in:
Nino Jagar
2023-07-19 18:15:58 +00:00
committed by Android (Google) Code Review
4 changed files with 375 additions and 13 deletions

View File

@@ -42,6 +42,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.assist.ActivityId;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -94,10 +95,12 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.GlobalWhitelistState;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.contentprotection.ContentProtectionBlocklistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.infra.AbstractMasterSystemService;
@@ -216,6 +219,8 @@ public class ContentCaptureManagerService extends
@Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
@Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
@@ -260,12 +265,15 @@ public class ContentCaptureManagerService extends
mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
mContentProtectionBlocklistManager.updateBlocklist(
mDevCfgContentProtectionAppsBlocklistSize);
mContentProtectionConsentManager = createContentProtectionConsentManager();
} else {
mContentProtectionBlocklistManager = null;
mContentProtectionConsentManager = null;
}
} else {
mContentProtectionServiceComponentName = null;
mContentProtectionBlocklistManager = null;
mContentProtectionConsentManager = null;
}
}
@@ -802,6 +810,17 @@ public class ContentCaptureManagerService extends
new ContentProtectionPackageManager(getContext()));
}
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
protected ContentProtectionConsentManager createContentProtectionConsentManager() {
// Same handler as used by AbstractMasterSystemService
return new ContentProtectionConsentManager(
BackgroundThread.getHandler(),
getContext().getContentResolver(),
LocalServices.getService(DevicePolicyManagerInternal.class));
}
@Nullable
private ComponentName getContentProtectionServiceComponentName() {
String flatComponentName = getContentProtectionServiceFlatComponentName();
@@ -1213,7 +1232,7 @@ public class ContentCaptureManagerService extends
isContentCaptureReceiverEnabled =
isContentCaptureReceiverEnabled(userId, packageName);
isContentProtectionReceiverEnabled =
isContentProtectionReceiverEnabled(packageName);
isContentProtectionReceiverEnabled(userId, packageName);
if (!isContentCaptureReceiverEnabled) {
// Full package is not allowlisted: check individual components next
@@ -1284,13 +1303,13 @@ public class ContentCaptureManagerService extends
@Override // from GlobalWhitelistState
public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
return isContentCaptureReceiverEnabled(userId, packageName)
|| isContentProtectionReceiverEnabled(packageName);
|| isContentProtectionReceiverEnabled(userId, packageName);
}
@Override // from GlobalWhitelistState
public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
return super.isWhitelisted(userId, componentName)
|| isContentProtectionReceiverEnabled(componentName.getPackageName());
|| isContentProtectionReceiverEnabled(userId, componentName.getPackageName());
}
private boolean isContentCaptureReceiverEnabled(
@@ -1298,9 +1317,11 @@ public class ContentCaptureManagerService extends
return super.isWhitelisted(userId, packageName);
}
private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
private boolean isContentProtectionReceiverEnabled(
@UserIdInt int userId, @NonNull String packageName) {
if (mContentProtectionServiceComponentName == null
|| mContentProtectionBlocklistManager == null) {
|| mContentProtectionBlocklistManager == null
|| mContentProtectionConsentManager == null) {
return false;
}
synchronized (mLock) {
@@ -1308,7 +1329,8 @@ public class ContentCaptureManagerService extends
return false;
}
}
return mContentProtectionBlocklistManager.isAllowed(packageName);
return mContentProtectionConsentManager.isConsentGranted(userId)
&& mContentProtectionBlocklistManager.isAllowed(packageName);
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 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.contentprotection;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
/**
* Manages consent for content protection.
*
* @hide
*/
public class ContentProtectionConsentManager {
private static final String TAG = "ContentProtectionConsentManager";
private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
@NonNull private final ContentResolver mContentResolver;
@NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
public final ContentObserver mContentObserver;
private volatile boolean mCachedPackageVerifierConsent;
public ContentProtectionConsentManager(
@NonNull Handler handler,
@NonNull ContentResolver contentResolver,
@NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) {
mContentResolver = contentResolver;
mDevicePolicyManagerInternal = devicePolicyManagerInternal;
mContentObserver = new SettingsObserver(handler);
contentResolver.registerContentObserver(
Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT),
/* notifyForDescendants= */ false,
mContentObserver,
UserHandle.USER_ALL);
mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
}
/**
* Returns true if all the consents are granted
*/
public boolean isConsentGranted(@UserIdInt int userId) {
return mCachedPackageVerifierConsent && !isUserOrganizationManaged(userId);
}
private boolean isPackageVerifierConsentGranted() {
// Not always cached internally
return Settings.Global.getInt(
mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
>= 1;
}
private boolean isUserOrganizationManaged(@UserIdInt int userId) {
// Cached internally
return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
}
private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
final String property = uri.getLastPathSegment();
if (property == null) {
return;
}
switch (property) {
case KEY_PACKAGE_VERIFIER_USER_CONSENT:
mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
return;
default:
Slog.w(TAG, "Ignoring unexpected property: " + property);
}
}
}
}

View File

@@ -42,6 +42,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.contentprotection.ContentProtectionBlocklistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.pm.UserManagerInternal;
@@ -91,6 +92,8 @@ public class ContentCaptureManagerServiceTest {
@Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
@Mock private ContentProtectionConsentManager mMockContentProtectionConsentManager;
private boolean mDevCfgEnableContentProtectionReceiver;
private int mContentProtectionBlocklistManagersCreated;
@@ -99,6 +102,8 @@ public class ContentCaptureManagerServiceTest {
private int mRemoteContentProtectionServicesCreated;
private int mContentProtectionConsentManagersCreated;
private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
private boolean mContentProtectionServiceInfoConstructorShouldThrow;
@@ -114,43 +119,51 @@ public class ContentCaptureManagerServiceTest {
}
@Test
public void constructor_contentProtection_flagDisabled_noBlocklistManager() {
public void constructor_contentProtection_flagDisabled_noManagers() {
assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
verifyZeroInteractions(mMockContentProtectionBlocklistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
public void constructor_contentProtection_componentNameNull_noBlocklistManager() {
public void constructor_contentProtection_componentNameNull_noManagers() {
mConfigDefaultContentProtectionService = null;
mContentCaptureManagerService = new TestContentCaptureManagerService();
assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
verifyZeroInteractions(mMockContentProtectionBlocklistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
public void constructor_contentProtection_componentNameBlank_noBlocklistManager() {
public void constructor_contentProtection_componentNameBlank_noManagers() {
mConfigDefaultContentProtectionService = " ";
mContentCaptureManagerService = new TestContentCaptureManagerService();
assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
verifyZeroInteractions(mMockContentProtectionBlocklistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
public void constructor_contentProtection_enabled_createsBlocklistManager() {
public void constructor_contentProtection_enabled_createsManagers() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -175,11 +188,13 @@ public class ContentCaptureManagerServiceTest {
USER_ID, PACKAGE_NAME);
assertThat(actual).isNull();
verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -211,11 +226,13 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual.contentProtectionOptions).isNotNull();
assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
assertThat(actual.whitelistedComponents).isNull();
verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -233,8 +250,23 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual.whitelistedComponents).isNull();
}
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionNotGranted() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
boolean actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -248,6 +280,7 @@ public class ContentCaptureManagerServiceTest {
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -271,11 +304,27 @@ public class ContentCaptureManagerServiceTest {
USER_ID, PACKAGE_NAME);
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionNotGranted() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
boolean actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
USER_ID, COMPONENT_NAME);
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -289,6 +338,7 @@ public class ContentCaptureManagerServiceTest {
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -312,16 +362,18 @@ public class ContentCaptureManagerServiceTest {
USER_ID, COMPONENT_NAME);
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@Test
public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
public void isContentProtectionReceiverEnabled_withoutManagers() {
boolean actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@@ -336,6 +388,7 @@ public class ContentCaptureManagerServiceTest {
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
}
@@ -423,5 +476,11 @@ public class ContentCaptureManagerServiceTest {
mRemoteContentProtectionServicesCreated++;
return mMockRemoteContentProtectionService;
}
@Override
protected ContentProtectionConsentManager createContentProtectionConsentManager() {
mContentProtectionConsentManagersCreated++;
return mMockContentProtectionConsentManager;
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2023 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.contentprotection;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.TestableContentResolver;
import android.testing.TestableContext;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
/**
* Test for {@link ContentProtectionConsentManager}.
*
* <p>Run with: {@code atest
* FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionConsentManagerTest}
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ContentProtectionConsentManagerTest {
private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
private static final Uri URI_PACKAGE_VERIFIER_USER_CONSENT =
Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT);
private static final int VALUE_TRUE = 1;
private static final int VALUE_FALSE = -1;
private static final int VALUE_DEFAULT = 0;
private static final int TEST_USER_ID = 1234;
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public final TestableContext mTestableContext =
new TestableContext(ApplicationProvider.getApplicationContext());
private final TestableContentResolver mTestableContentResolver =
mTestableContext.getContentResolver();
@Mock private ContentResolver mMockContentResolver;
@Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal;
@Test
public void constructor_registersContentObserver() {
ContentProtectionConsentManager manager =
createContentProtectionConsentManager(mMockContentResolver);
assertThat(manager.mContentObserver).isNotNull();
verify(mMockContentResolver)
.registerContentObserver(
URI_PACKAGE_VERIFIER_USER_CONSENT,
/* notifyForDescendants= */ false,
manager.mContentObserver,
UserHandle.USER_ALL);
}
@Test
public void isConsentGranted_packageVerifierNotGranted() {
ContentProtectionConsentManager manager =
createContentProtectionConsentManager(VALUE_FALSE);
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
verifyZeroInteractions(mMockDevicePolicyManagerInternal);
}
@Test
public void isConsentGranted_packageVerifierGranted_userNotManaged() {
ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
}
@Test
public void isConsentGranted_packageVerifierGranted_userManaged() {
when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
.thenReturn(true);
ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
}
@Test
public void isConsentGranted_packageVerifierDefault() {
ContentProtectionConsentManager manager =
createContentProtectionConsentManager(VALUE_DEFAULT);
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
verifyZeroInteractions(mMockDevicePolicyManagerInternal);
}
@Test
public void contentObserver() throws Exception {
ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
Settings.Global.putInt(
mTestableContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_FALSE);
// Observer has to be called manually, mTestableContentResolver is not propagating
manager.mContentObserver.onChange(
/* selfChange= */ false, URI_PACKAGE_VERIFIER_USER_CONSENT, TEST_USER_ID);
boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
assertThat(firstActual).isTrue();
assertThat(secondActual).isFalse();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
}
private ContentProtectionConsentManager createContentProtectionConsentManager(
ContentResolver contentResolver) {
return new ContentProtectionConsentManager(
new Handler(Looper.getMainLooper()),
contentResolver,
mMockDevicePolicyManagerInternal);
}
private ContentProtectionConsentManager createContentProtectionConsentManager(
int valuePackageVerifierUserConsent) {
Settings.Global.putInt(
mTestableContentResolver,
KEY_PACKAGE_VERIFIER_USER_CONSENT,
valuePackageVerifierUserConsent);
return createContentProtectionConsentManager(mTestableContentResolver);
}
}