blob: 6ac4d138c9d1b9dabcfb19b035ed221a3a65d627 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
// TODO(b/169883602): This is purposely a different package from the path so that it can access
// AppSearchImpl methods without having to make methods public. This should be moved into a proper
// package once AppSearchImpl-VisibilityStore's dependencies are refactored.
package com.android.server.appsearch.external.localstorage;
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.appsearch.PackageIdentifier;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.visibilitystore.VisibilityStore;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import java.util.Collections;
import java.util.Map;
public class VisibilityStoreTest {
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
private Context mContext;
private VisibilityStore mVisibilityStore;
private int mUid;
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
mContext = new ContextWrapper(context) {
@Override
public Context createContextAsUser(UserHandle user, int flags) {
return new ContextWrapper(super.createContextAsUser(user, flags)) {
@Override
public PackageManager getPackageManager() {
return getMockPackageManager(user);
}
};
}
@Override
public PackageManager getPackageManager() {
return createContextAsUser(getUser(), /*flags=*/ 0).getPackageManager();
}
};
// Give ourselves global query permissions
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
mContext,
/*logger=*/ null);
mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
mVisibilityStore = appSearchImpl.getVisibilityStoreLocked();
}
/**
* Make sure that we don't conflict with any special characters that AppSearchImpl has reserved.
*/
@Test
public void testValidPackageName() {
assertThat(VisibilityStore.PACKAGE_NAME)
.doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
assertThat(VisibilityStore.PACKAGE_NAME)
.doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
}
/**
* Make sure that we don't conflict with any special characters that AppSearchImpl has reserved.
*/
@Test
public void testValidDatabaseName() {
assertThat(VisibilityStore.DATABASE_NAME)
.doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
assertThat(VisibilityStore.DATABASE_NAME)
.doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
}
@Test
public void testSetVisibility_platformSurfaceable() throws Exception {
// Make sure we have global query privileges
PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
when(mockPackageManager
.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
.thenReturn(PERMISSION_GRANTED);
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema2"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema1",
mContext.getPackageName(),
mUid))
.isFalse();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema2",
mContext.getPackageName(),
mUid))
.isFalse();
// New .setVisibility() call completely overrides previous visibility settings.
// So "schema2" isn't preserved.
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema3"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema1",
mContext.getPackageName(),
mUid))
.isFalse();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema2",
mContext.getPackageName(),
mUid))
.isTrue();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema3",
mContext.getPackageName(),
mUid))
.isFalse();
// Everything defaults to visible again.
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema1",
mContext.getPackageName(),
mUid))
.isTrue();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema2",
mContext.getPackageName(),
mUid))
.isTrue();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package",
"database",
"prefix/schema3",
mContext.getPackageName(),
mUid))
.isTrue();
}
@Test
public void testSetVisibility_packageAccessible() throws Exception {
// Values for a "foo" client
String packageNameFoo = "packageFoo";
byte[] sha256CertFoo = new byte[] {10};
int uidFoo = 1;
// Values for a "bar" client
String packageNameBar = "packageBar";
byte[] sha256CertBar = new byte[] {100};
int uidBar = 2;
// Can't be the same value as uidFoo nor uidBar
int uidNotFooOrBar = 3;
// Make sure none of them have global query privileges
PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
when(mockPackageManager
.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
.thenReturn(PERMISSION_DENIED);
when(mockPackageManager
.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameBar))
.thenReturn(PERMISSION_DENIED);
// By default, a schema isn't package accessible.
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
.isFalse();
// Grant package access
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"prefix/schemaFoo",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo)),
"prefix/schemaBar",
ImmutableList.of(new PackageIdentifier(packageNameBar, sha256CertBar))));
// Should fail if PackageManager doesn't see that it has the proper certificate
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenReturn(uidFoo);
when(mockPackageManager.hasSigningCertificate(
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(false);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
// Should fail if PackageManager doesn't think the package belongs to the uid
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenReturn(uidNotFooOrBar);
when(mockPackageManager.hasSigningCertificate(
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
// But if uid and certificate match, then we should have access
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenReturn(uidFoo);
when(mockPackageManager.hasSigningCertificate(
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isTrue();
when(mockPackageManager.getPackageUid(eq(packageNameBar), /*flags=*/ anyInt()))
.thenReturn(uidBar);
when(mockPackageManager.hasSigningCertificate(
packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
.isTrue();
// New .setVisibility() call completely overrides previous visibility settings. So
// "schemaBar" settings aren't preserved.
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"prefix/schemaFoo",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenReturn(uidFoo);
when(mockPackageManager.hasSigningCertificate(
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isTrue();
when(mockPackageManager.getPackageUid(eq(packageNameBar), /*flags=*/ anyInt()))
.thenReturn(uidBar);
when(mockPackageManager.hasSigningCertificate(
packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
.isFalse();
}
@Test
public void testIsSchemaSearchableByCaller_packageAccessibilityHandlesNameNotFoundException()
throws Exception {
// Values for a "foo" client
String packageNameFoo = "packageFoo";
byte[] sha256CertFoo = new byte[] {10};
int uidFoo = 1;
// Pretend we can't find the Foo package.
PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenThrow(new PackageManager.NameNotFoundException());
// Make sure "foo" doesn't have global query privileges
when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
.thenReturn(PERMISSION_DENIED);
// Grant package access
mVisibilityStore.setVisibility(
"package",
"database",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"prefix/schemaFoo",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
// If we can't verify the Foo package that has access, assume it doesn't have access.
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
}
@Test
public void testEmptyPrefix() throws Exception {
// Values for a "foo" client
String packageNameFoo = "packageFoo";
byte[] sha256CertFoo = new byte[] {10};
int uidFoo = 1;
// Set it up such that the test package has global query privileges, but "foo" doesn't.
PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
when(mockPackageManager.checkPermission(
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
.thenReturn(PERMISSION_GRANTED);
when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
.thenReturn(PERMISSION_DENIED);
mVisibilityStore.setVisibility(
/*packageName=*/ "",
/*databaseName=*/ "",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"schema",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
/*packageName=*/ "",
/*databaseName=*/ "",
"schema",
mContext.getPackageName(),
mUid))
.isTrue();
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
.thenReturn(uidFoo);
when(mockPackageManager.hasSigningCertificate(
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
/*packageName=*/ "",
/*databaseName=*/ "",
"schema",
packageNameFoo,
uidFoo))
.isTrue();
}
@NonNull
private PackageManager getMockPackageManager(@NonNull UserHandle user) {
PackageManager pm = mMockPackageManagers.get(user);
if (pm == null) {
pm = Mockito.mock(PackageManager.class);
mMockPackageManagers.put(user, pm);
}
return pm;
}
}