Merge "Add a platform version of VisibilityStore."
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO(b/169883602): This is purposely a different package from the path so that it can access
|
||||
// AppSearchImpl's methods without having to make them public. This should be moved into a proper
|
||||
// package once AppSearchImpl-VisibilityStore's dependencies are refactored.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.appsearch.AppSearchResult;
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
@@ -25,10 +29,10 @@ import android.app.appsearch.exceptions.AppSearchException;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -56,8 +60,16 @@ import java.util.Set;
|
||||
*
|
||||
* <p>NOTE: This class holds an instance of AppSearchImpl and AppSearchImpl holds an instance of
|
||||
* this class. Take care to not cause any circular dependencies.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
class VisibilityStore {
|
||||
public class VisibilityStore {
|
||||
|
||||
private static final String TAG = "AppSearchVisibilityStore";
|
||||
|
||||
/** No-op user id that won't have any visibility settings. */
|
||||
public static final int NO_OP_USER_ID = -1;
|
||||
|
||||
/** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
|
||||
private static final String VISIBILITY_TYPE = "VisibilityType";
|
||||
|
||||
@@ -124,8 +136,8 @@ class VisibilityStore {
|
||||
.build();
|
||||
|
||||
/**
|
||||
* These cannot have any of the special characters used by AppSearchImpl (e.g. {@link
|
||||
* AppSearchImpl#PACKAGE_DELIMITER} or {@link AppSearchImpl#DATABASE_DELIMITER}.
|
||||
* These cannot have any of the special characters used by AppSearchImpl (e.g. {@code
|
||||
* AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}.
|
||||
*/
|
||||
static final String PACKAGE_NAME = "VS#Pkg";
|
||||
|
||||
@@ -149,11 +161,15 @@ class VisibilityStore {
|
||||
|
||||
private final AppSearchImpl mAppSearchImpl;
|
||||
|
||||
// Context of the system service.
|
||||
private final Context mContext;
|
||||
|
||||
// User ID of the caller who we're checking visibility settings for.
|
||||
private final int mUserId;
|
||||
|
||||
// UID of the package that has platform-query privileges, i.e. can query for all
|
||||
// platform-surfaceable content.
|
||||
private int mGlobalQuerierPackageUid;
|
||||
private int mGlobalQuerierUid;
|
||||
|
||||
/**
|
||||
* Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas
|
||||
@@ -180,20 +196,15 @@ class VisibilityStore {
|
||||
*
|
||||
* @param appSearchImpl AppSearchImpl instance
|
||||
*/
|
||||
VisibilityStore(
|
||||
public VisibilityStore(
|
||||
@NonNull AppSearchImpl appSearchImpl,
|
||||
@NonNull Context context,
|
||||
@UserIdInt int userId,
|
||||
@NonNull String globalQuerierPackage) {
|
||||
mAppSearchImpl = appSearchImpl;
|
||||
mContext = context;
|
||||
mGlobalQuerierPackageUid = Process.INVALID_UID;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
// This should always pass since we should only allow platform access on S+ (the first
|
||||
// version that AppSearch is offered on).
|
||||
mGlobalQuerierPackageUid =
|
||||
Api24Impl.getGlobalQuerierPackageUid(context, globalQuerierPackage);
|
||||
}
|
||||
mUserId = userId;
|
||||
mGlobalQuerierUid = getGlobalQuerierUid(globalQuerierPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,7 +368,9 @@ class VisibilityStore {
|
||||
Preconditions.checkNotNull(prefix);
|
||||
Preconditions.checkNotNull(prefixedSchema);
|
||||
|
||||
if (callerUid == mGlobalQuerierPackageUid
|
||||
// We compare appIds here rather than direct uids because the package's uid may change based
|
||||
// on the user that's running.
|
||||
if (UserHandle.isSameApp(mGlobalQuerierUid, callerUid)
|
||||
&& isSchemaPlatformSurfaceable(prefix, prefixedSchema)) {
|
||||
return true;
|
||||
}
|
||||
@@ -414,26 +427,21 @@ class VisibilityStore {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) {
|
||||
// PackageManager.hasSigningCertificate is only available on P+
|
||||
// This should never fail since we should only allow package access on S+ (the first
|
||||
// version that AppSearch is offered on). But just in case, default to no package
|
||||
// access.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
|
||||
// Check that the caller uid matches this allowlisted PackageIdentifier.
|
||||
if (Api24Impl.getPackageUid(mContext, packageIdentifier.getPackageName())
|
||||
!= callerUid) {
|
||||
// TODO(b/169883602): Consider caching the UIDs of packages. Looking this up in the
|
||||
// package manager could be costly. We would also need to update the cache on
|
||||
// package-removals.
|
||||
if (getPackageUidAsUser(packageIdentifier.getPackageName()) != callerUid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the package also has the matching certificate
|
||||
if (Api28Impl.hasSigningCertificate(
|
||||
mContext,
|
||||
packageIdentifier.getPackageName(),
|
||||
packageIdentifier.getSha256Certificate())) {
|
||||
if (mContext.getPackageManager()
|
||||
.hasSigningCertificate(
|
||||
packageIdentifier.getPackageName(),
|
||||
packageIdentifier.getSha256Certificate(),
|
||||
PackageManager.CERT_INPUT_SHA256)) {
|
||||
// The caller has the right package name and right certificate!
|
||||
return true;
|
||||
}
|
||||
@@ -448,7 +456,7 @@ class VisibilityStore {
|
||||
*
|
||||
* <p>{@link #initialize()} must be called after this.
|
||||
*/
|
||||
void handleReset() {
|
||||
public void handleReset() {
|
||||
mNotPlatformSurfaceableMap.clear();
|
||||
mPackageAccessibleMap.clear();
|
||||
}
|
||||
@@ -464,83 +472,40 @@ class VisibilityStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class around API 24 methods.
|
||||
*
|
||||
* <p>Even though wrapping a call to a method from an API above minSdk inside an SDK_INT check
|
||||
* makes it runtime safe, it is not optimal. When ART tries to optimize a class, it will do so
|
||||
* regardless of the execution path, and will fail if it tries to resolve a method at a higher
|
||||
* API if that method is being referenced somewhere in the class, even if that method would
|
||||
* never be called at runtime due to the SDK_INT check. ART will however only try to optimize a
|
||||
* class the first time it's referenced at runtime, this means if we wrap our above minSdk
|
||||
* method calls inside classes that are only referenced at runtime at the appropriate API level,
|
||||
* then we guarantee the ability to resolve all the methods.
|
||||
* Finds the uid of the {@code globalQuerierPackage}. {@code globalQuerierPackage} must be a
|
||||
* pre-installed, system app. Returns {@link Process#INVALID_UID} if unable to find the UID.
|
||||
*/
|
||||
@RequiresApi(24)
|
||||
private static class Api24Impl {
|
||||
private Api24Impl() {}
|
||||
|
||||
/**
|
||||
* Finds the UID of the {@code globalQuerierPackage}. {@code globalQuerierPackage} must be a
|
||||
* pre-installed, system app. Returns {@link Process#INVALID_UID} if unable to find the UID.
|
||||
*/
|
||||
static int getGlobalQuerierPackageUid(
|
||||
@NonNull Context context, @NonNull String globalQuerierPackage) {
|
||||
try {
|
||||
// TODO(b/169883602): In framework, this should be UserHandle.isSameApp or
|
||||
// packageManager.getPackageUidAsUser().
|
||||
int flags =
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS
|
||||
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
|
||||
| PackageManager.MATCH_SYSTEM_ONLY;
|
||||
return context.getPackageManager().getPackageUid(globalQuerierPackage, flags);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Global querier doesn't exist.
|
||||
}
|
||||
return Process.INVALID_UID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable
|
||||
* to find the UID.
|
||||
*/
|
||||
static int getPackageUid(@NonNull Context context, @NonNull String packageName) {
|
||||
try {
|
||||
// TODO(b/169883602): In framework, this should be UserHandle.isSameApp or
|
||||
// packageManager.getPackageUidAsUser().
|
||||
return context.getPackageManager().getPackageUid(packageName, /*flags=*/ 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Global querier doesn't exist.
|
||||
}
|
||||
return Process.INVALID_UID;
|
||||
private int getGlobalQuerierUid(@NonNull String globalQuerierPackage) {
|
||||
try {
|
||||
int flags =
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS
|
||||
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
|
||||
| PackageManager.MATCH_SYSTEM_ONLY;
|
||||
// It doesn't matter that we're using the caller's userId here. We'll eventually check
|
||||
// that the two uids in question belong to the same appId.
|
||||
return mContext.getPackageManager()
|
||||
.getPackageUidAsUser(globalQuerierPackage, flags, mUserId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Global querier doesn't exist.
|
||||
Log.i(
|
||||
TAG,
|
||||
"AppSearch global querier package not found on device: '"
|
||||
+ globalQuerierPackage
|
||||
+ "'");
|
||||
}
|
||||
return Process.INVALID_UID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class around API 28 methods.
|
||||
*
|
||||
* <p>Even though wrapping a call to a method from an API above minSdk inside an SDK_INT check
|
||||
* makes it runtime safe, it is not optimal. When ART tries to optimize a class, it will do so
|
||||
* regardless of the execution path, and will fail if it tries to resolve a method at a higher
|
||||
* API if that method is being referenced somewhere in the class, even if that method would
|
||||
* never be called at runtime due to the SDK_INT check. ART will however only try to optimize a
|
||||
* class the first time it's referenced at runtime, this means if we wrap our above minSdk
|
||||
* method calls inside classes that are only referenced at runtime at the appropriate API level,
|
||||
* then we guarantee the ability to resolve all the methods.
|
||||
* Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
|
||||
* find the UID.
|
||||
*/
|
||||
@RequiresApi(28)
|
||||
private static class Api28Impl {
|
||||
private Api28Impl() {}
|
||||
|
||||
/**
|
||||
* Returns whether the {@code packageName} has been signed with {@code sha256Certificate}.
|
||||
*/
|
||||
static boolean hasSigningCertificate(
|
||||
@NonNull Context context,
|
||||
@NonNull String packageName,
|
||||
@NonNull byte[] sha256Certificate) {
|
||||
return context.getPackageManager()
|
||||
.hasSigningCertificate(
|
||||
packageName, sha256Certificate, PackageManager.CERT_INPUT_SHA256);
|
||||
private int getPackageUidAsUser(@NonNull String packageName) {
|
||||
try {
|
||||
return mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Package doesn't exist, continue
|
||||
}
|
||||
return Process.INVALID_UID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,8 @@ public final class AppSearchImpl {
|
||||
mIcingSearchEngineLocked = new IcingSearchEngine(options);
|
||||
|
||||
mVisibilityStoreLocked =
|
||||
new VisibilityStore(this, context, globalQuerierPackage);
|
||||
new VisibilityStore(
|
||||
this, context, userId, globalQuerierPackage);
|
||||
|
||||
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
|
||||
SchemaProto schemaProto;
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* 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's methods without having to make them public. This should be replaced by proper
|
||||
// global query integration tests that can test AppSearchImpl-VisibilityStore integration logic.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.PackageIdentifier;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
import android.app.appsearch.SearchSpec;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */
|
||||
public class AppSearchImplPlatformTest {
|
||||
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
|
||||
private MockPackageManager mMockPackageManager = new MockPackageManager();
|
||||
private Context mContext;
|
||||
private AppSearchImpl mAppSearchImpl;
|
||||
private int mGlobalQuerierUid;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
mContext =
|
||||
new ContextWrapper(context) {
|
||||
@Override
|
||||
public PackageManager getPackageManager() {
|
||||
return mMockPackageManager.getMockPackageManager();
|
||||
}
|
||||
};
|
||||
|
||||
// Give ourselves global query permissions
|
||||
mAppSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
mContext.getPackageName());
|
||||
mGlobalQuerierUid =
|
||||
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
}
|
||||
/**
|
||||
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
|
||||
* test until we have official support for multiple-apps indexing at once.
|
||||
*/
|
||||
@Test
|
||||
public void testGlobalQueryWithMultiplePackages_noPackageFilters() throws Exception {
|
||||
// Insert package1 schema
|
||||
List<AppSearchSchema> schema1 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package1",
|
||||
"database1",
|
||||
schema1,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert package2 schema
|
||||
List<AppSearchSchema> schema2 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package2",
|
||||
"database2",
|
||||
schema2,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert package1 document
|
||||
GenericDocument document1 =
|
||||
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("package1", "database1", document1);
|
||||
|
||||
// Insert package2 document
|
||||
GenericDocument document2 =
|
||||
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("package2", "database2", document2);
|
||||
|
||||
// No query filters specified, global query can retrieve all documents.
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
|
||||
SearchResultPage searchResultPage =
|
||||
mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
|
||||
assertThat(searchResultPage.getResults()).hasSize(2);
|
||||
|
||||
// Document2 will be first since it got indexed later and has a "better", aka more recent
|
||||
// score.
|
||||
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
|
||||
assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
|
||||
* test until we have official support for multiple-apps indexing at once.
|
||||
*/
|
||||
@Test
|
||||
public void testGlobalQueryWithMultiplePackages_withPackageFilters() throws Exception {
|
||||
// Insert package1 schema
|
||||
List<AppSearchSchema> schema1 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package1",
|
||||
"database1",
|
||||
schema1,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert package2 schema
|
||||
List<AppSearchSchema> schema2 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package2",
|
||||
"database2",
|
||||
schema2,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert package1 document
|
||||
GenericDocument document1 =
|
||||
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("package1", "database1", document1);
|
||||
|
||||
// Insert package2 document
|
||||
GenericDocument document2 =
|
||||
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("package2", "database2", document2);
|
||||
|
||||
// "package1" filter specified
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder()
|
||||
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
|
||||
.addFilterPackageNames("package1")
|
||||
.build();
|
||||
SearchResultPage searchResultPage =
|
||||
mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
|
||||
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1);
|
||||
|
||||
// "package2" filter specified
|
||||
searchSpec =
|
||||
new SearchSpec.Builder()
|
||||
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
|
||||
.addFilterPackageNames("package2")
|
||||
.build();
|
||||
searchResultPage =
|
||||
mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
|
||||
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
|
||||
// Values for a "foo" client
|
||||
String packageNameFoo = "packageFoo";
|
||||
byte[] sha256CertFoo = new byte[] {10};
|
||||
int uidFoo = 1;
|
||||
|
||||
// Make sure foo package will pass package manager checks.
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
// Set schema1
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"schema1",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// "schema1" is platform hidden now and package visible to package1
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// Add a new schema, and include the already-existing "schema1"
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("schema1").build(),
|
||||
new AppSearchSchema.Builder("schema2").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"schema1",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Check that "schema1" still has the same visibility settings
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// "schema2" has default visibility settings
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema2", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema2", uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
|
||||
// Values for a "foo" client
|
||||
String packageNameFoo = "packageFoo";
|
||||
byte[] sha256CertFoo = new byte[] {10};
|
||||
int uidFoo = 1;
|
||||
|
||||
// Make sure foo package will pass package manager checks.
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"schema1",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// "schema1" is platform hidden now and package accessible
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// Remove "schema1" by force overriding
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
/*schemas=*/ Collections.emptyList(),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ true);
|
||||
|
||||
// Check that "schema1" is no longer considered platform hidden or package accessible
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// Add "schema1" back, it gets default visibility settings which means it's not platform
|
||||
// hidden and not package accessible
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_defaultPlatformVisible() throws Exception {
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_platformHidden() throws Exception {
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_defaultNotPackageAccessible() throws Exception {
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", /*callerUid=*/ 42))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_packageAccessible() throws Exception {
|
||||
// Values for a "foo" client
|
||||
String packageNameFoo = "packageFoo";
|
||||
byte[] sha256CertFoo = new byte[] {10};
|
||||
int uidFoo = 1;
|
||||
|
||||
// Make sure foo package will pass package manager checks.
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
String prefix = AppSearchImpl.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"Schema",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "Schema", uidFoo))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 AppSearchImplTest
|
||||
// can use it without an extra import. This should be moved into a proper package once
|
||||
// AppSearchImpl-VisibilityStore's dependencies are refactored.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Mock to help test package name, UID, and certificate verification. */
|
||||
public class MockPackageManager {
|
||||
|
||||
@Mock private PackageManager mMockPackageManager;
|
||||
|
||||
public MockPackageManager() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public PackageManager getMockPackageManager() {
|
||||
return mMockPackageManager;
|
||||
}
|
||||
|
||||
/** Mock a NameNotFoundException if the package name isn't installed. */
|
||||
public void mockThrowsNameNotFoundException(String packageName) {
|
||||
try {
|
||||
when(mMockPackageManager.getPackageUidAsUser(eq(packageName), /*userId=*/ anyInt()))
|
||||
.thenThrow(new PackageManager.NameNotFoundException());
|
||||
when(mMockPackageManager.getPackageUidAsUser(
|
||||
eq(packageName), /*flags=*/ anyInt(), /*userId=*/ anyInt()))
|
||||
.thenThrow(new PackageManager.NameNotFoundException());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Shouldn't ever happen since we're mocking the exception
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Mocks that {@code uid} contains the {@code packageName} */
|
||||
public void mockGetPackageUidAsUser(String packageName, @UserIdInt int callerUserId, int uid) {
|
||||
try {
|
||||
when(mMockPackageManager.getPackageUidAsUser(eq(packageName), eq(callerUserId)))
|
||||
.thenReturn(uid);
|
||||
when(mMockPackageManager.getPackageUidAsUser(
|
||||
eq(packageName), /*flags=*/ anyInt(), eq(callerUserId)))
|
||||
.thenReturn(uid);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Shouldn't ever happen since we're mocking the method.
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Mocks that {@code packageName} has been signed with {@code sha256Cert}. */
|
||||
public void mockAddSigningCertificate(String packageName, byte[] sha256Cert) {
|
||||
when(mMockPackageManager.hasSigningCertificate(
|
||||
packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256))
|
||||
.thenReturn(true);
|
||||
}
|
||||
|
||||
/** Mocks that {@code packageName} has NOT been signed with {@code sha256Cert}. */
|
||||
public void mockRemoveSigningCertificate(String packageName, byte[] sha256Cert) {
|
||||
when(mMockPackageManager.hasSigningCertificate(
|
||||
packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256))
|
||||
.thenReturn(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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 and VisibilityStore 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 com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.PackageIdentifier;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
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 java.util.Collections;
|
||||
|
||||
public class VisibilityStoreTest {
|
||||
|
||||
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
|
||||
private MockPackageManager mMockPackageManager = new MockPackageManager();
|
||||
private Context mContext;
|
||||
private AppSearchImpl mAppSearchImpl;
|
||||
private VisibilityStore mVisibilityStore;
|
||||
private int mGlobalQuerierUid;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
mContext =
|
||||
new ContextWrapper(context) {
|
||||
@Override
|
||||
public PackageManager getPackageManager() {
|
||||
return mMockPackageManager.getMockPackageManager();
|
||||
}
|
||||
};
|
||||
|
||||
// Give ourselves global query permissions
|
||||
mAppSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
/*globalQuerierPackage=*/ mContext.getPackageName());
|
||||
mGlobalQuerierUid =
|
||||
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
|
||||
mVisibilityStore = mAppSearchImpl.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(
|
||||
"" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
|
||||
assertThat(VisibilityStore.PACKAGE_NAME)
|
||||
.doesNotContain(
|
||||
""
|
||||
+ AppSearchImpl
|
||||
.DATABASE_DELIMITER); // Convert the chars to CharSequences
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that we don't conflict with any special characters that AppSearchImpl has reserved.
|
||||
*/
|
||||
@Test
|
||||
public void testValidDatabaseName() {
|
||||
assertThat(VisibilityStore.DATABASE_NAME)
|
||||
.doesNotContain(
|
||||
"" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
|
||||
assertThat(VisibilityStore.DATABASE_NAME)
|
||||
.doesNotContain(
|
||||
""
|
||||
+ AppSearchImpl
|
||||
.DATABASE_DELIMITER); // Convert the chars to CharSequences
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_platformSurfaceable() throws Exception {
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
|
||||
"prefix/schema1", "prefix/schema2"),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
|
||||
// New .setVisibility() call completely overrides previous visibility settings. So
|
||||
// "schema2" isn't preserved.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
|
||||
"prefix/schema1", "prefix/schema3"),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema3", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
|
||||
// Everything defaults to visible again.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema3", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSchemaSearchableByCaller_platformQuerierHandlesNameNotFoundException()
|
||||
throws Exception {
|
||||
// Initialized the VisibilityStore with this context's package name as the global querier.
|
||||
mMockPackageManager.mockThrowsNameNotFoundException(mContext.getPackageName());
|
||||
|
||||
// Create a new VisibilityStore instance since we look up the UID on initialization
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
/*globalQuerierPackage=*/ mContext.getPackageName());
|
||||
VisibilityStore visibilityStore = appSearchImpl.getVisibilityStoreLocked();
|
||||
|
||||
// Use some arbitrary callerUid. If we can't find the global querier's uid though,
|
||||
// nothing should be platform surfaceable.
|
||||
assertThat(
|
||||
visibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", /*callerUid=*/ 0))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
// By default, a schema isn't package accessible.
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", uidBar))
|
||||
.isFalse();
|
||||
|
||||
// Grant package access
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*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
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockRemoveSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// Should fail if PackageManager doesn't think the package belongs to the uid
|
||||
mMockPackageManager.mockGetPackageUidAsUser(
|
||||
packageNameFoo, mContext.getUserId(), uidNotFooOrBar);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// But if uid and certificate match, then we should have access
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", uidBar))
|
||||
.isTrue();
|
||||
|
||||
// New .setVisibility() call completely overrides previous visibility settings. So
|
||||
// "schemaBar" settings aren't preserved.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"prefix/schemaFoo",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", 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.
|
||||
mMockPackageManager.mockThrowsNameNotFoundException(packageNameFoo);
|
||||
|
||||
// Grant package access
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
/*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(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyPrefix() throws Exception {
|
||||
// Values for a "foo" client
|
||||
String packageNameFoo = "packageFoo";
|
||||
byte[] sha256CertFoo = new byte[] {10};
|
||||
int uidFoo = 1;
|
||||
|
||||
mVisibilityStore.setVisibility(
|
||||
/*prefix=*/ "",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"schema",
|
||||
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
|
||||
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
/*prefix=*/ "", "schema", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(mVisibilityStore.isSchemaSearchableByCaller(/*prefix=*/ "", "schema", uidFoo))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user