Merge changes I2cf2e739,I9e4bd242,Ic8b93a8c,Ibccde458,I04942e14, ... into sc-dev

* changes:
  Fix domain verify restore and add signature check
  Support serializing package signatures for domain verification state
  Add domain verification CTS to TEST_MAPPING
  Fix DomainVerificationService deadlock
  Check installed and enabled state for package domain approval
  Revoke domain user selection when approved through shell
This commit is contained in:
Winson Chiu
2021-04-09 17:05:50 +00:00
committed by Android (Google) Code Review
17 changed files with 1228 additions and 512 deletions

View File

@@ -32,6 +32,7 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.pm.overlay.OverlayPaths; import android.content.pm.overlay.OverlayPaths;
import android.content.pm.parsing.ParsingPackageRead;
import android.content.pm.parsing.component.ParsedMainComponent; import android.content.pm.parsing.component.ParsedMainComponent;
import android.os.BaseBundle; import android.os.BaseBundle;
import android.os.Debug; import android.os.Debug;
@@ -53,7 +54,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer; import org.xmlpull.v1.XmlSerializer;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -310,6 +310,20 @@ public class PackageUserState {
return result; return result;
} }
public boolean isPackageEnabled(@NonNull ParsingPackageRead pkg) {
switch (this.enabled) {
case COMPONENT_ENABLED_STATE_ENABLED:
return true;
case COMPONENT_ENABLED_STATE_DISABLED:
case COMPONENT_ENABLED_STATE_DISABLED_USER:
case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
return false;
default:
case COMPONENT_ENABLED_STATE_DEFAULT:
return pkg.isEnabled();
}
}
public boolean isEnabled(ComponentInfo componentInfo, int flags) { public boolean isEnabled(ComponentInfo componentInfo, int flags) {
return isEnabled(componentInfo.applicationInfo.enabled, componentInfo.enabled, return isEnabled(componentInfo.applicationInfo.enabled, componentInfo.enabled,
componentInfo.name, flags); componentInfo.name, flags);

View File

@@ -7,6 +7,12 @@
"include-filter": "com.android.server.pm.test.verify.domain" "include-filter": "com.android.server.pm.test.verify.domain"
} }
] ]
},
{
"name": "CtsDomainVerificationDeviceTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
} }
] ]
} }

View File

@@ -356,6 +356,7 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils; import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.permission.persistence.RuntimePermissionsPersistence;
@@ -1686,9 +1687,8 @@ public class PackageManagerService extends IPackageManager.Stub
private final DomainVerificationConnection mDomainVerificationConnection = private final DomainVerificationConnection mDomainVerificationConnection =
new DomainVerificationConnection(); new DomainVerificationConnection();
private class DomainVerificationConnection implements private class DomainVerificationConnection implements DomainVerificationService.Connection,
DomainVerificationService.Connection, DomainVerificationProxyV1.Connection, DomainVerificationProxyV1.Connection, DomainVerificationProxyV2.Connection {
DomainVerificationProxyV2.Connection {
@Override @Override
public void scheduleWriteSettings() { public void scheduleWriteSettings() {
@@ -1732,22 +1732,77 @@ public class PackageManagerService extends IPackageManager.Stub
return callingUid == getPackageUid(packageName, 0, callingUserId); return callingUid == getPackageUid(packageName, 0, callingUserId);
} }
@Nullable
@Override
public PackageSetting getPackageSettingLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackageSetting(pkgName);
}
@Nullable
@Override
public AndroidPackage getPackageLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackage(pkgName);
}
@Nullable @Nullable
@Override @Override
public AndroidPackage getPackage(@NonNull String packageName) { public AndroidPackage getPackage(@NonNull String packageName) {
return getPackageLocked(packageName); return PackageManagerService.this.getPackage(packageName);
}
@NonNull
@Override
public void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block) {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
block.accept(snapshot::getPackageSetting);
}
} else {
block.accept(snapshot::getPackageSetting);
}
}
@Override
public <Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block) {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
}
@Override
public <ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
block.accept(snapshot::getPackageSetting);
}
} else {
block.accept(snapshot::getPackageSetting);
}
}
@Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,
PackageSetting>, Output, ExceptionType> block) throws ExceptionType {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
} }
@Override @Override
@@ -2012,7 +2067,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Cached attributes. The names in this class are the same as the // Cached attributes. The names in this class are the same as the
// names in PackageManagerService; see that class for documentation. // names in PackageManagerService; see that class for documentation.
private final Settings mSettings; protected final Settings mSettings;
private final WatchedSparseIntArray mIsolatedOwners; private final WatchedSparseIntArray mIsolatedOwners;
private final WatchedArrayMap<String, AndroidPackage> mPackages; private final WatchedArrayMap<String, AndroidPackage> mPackages;
private final WatchedArrayMap<ComponentName, ParsedInstrumentation> private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
@@ -4669,6 +4724,17 @@ public class PackageManagerService extends IPackageManager.Stub
mLock = mService.mLock; mLock = mService.mLock;
} }
/**
* Explicilty snapshot {@link Settings#mPackages} for cases where the caller must not lock
* in order to get package data. It is expected that the caller locks itself to be able
* to block on changes to the package data and bring itself up to date once the change
* propagates to it. Use with heavy caution.
* @return
*/
private Map<String, PackageSetting> snapshotPackageSettings() {
return mSettings.snapshot().mPackages;
}
public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
String resolvedType, int flags, int userId, int callingUid, String resolvedType, int flags, int userId, int callingUid,
String instantAppPkgName) { String instantAppPkgName) {
@@ -4800,7 +4866,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Compute read-only functions, based on live data. This attribute may be modified multiple // Compute read-only functions, based on live data. This attribute may be modified multiple
// times during the PackageManagerService constructor but it should not be modified thereafter. // times during the PackageManagerService constructor but it should not be modified thereafter.
private Computer mLiveComputer; private ComputerLocked mLiveComputer;
// A lock-free cache for frequently called functions. // A lock-free cache for frequently called functions.
private volatile Computer mSnapshotComputer; private volatile Computer mSnapshotComputer;
// If true, the snapshot is invalid (stale). The attribute is static since it may be // If true, the snapshot is invalid (stale). The attribute is static since it may be
@@ -21760,6 +21826,7 @@ public class PackageManagerService extends IPackageManager.Stub
clearPackagePreferredActivities(ps.name, nextUserId); clearPackagePreferredActivities(ps.name, nextUserId);
mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs, mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
nextUserId); nextUserId);
mDomainVerificationManager.clearPackageForUser(ps.name, nextUserId);
} }
if (outInfo != null) { if (outInfo != null) {

View File

@@ -28,6 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Binder; import android.os.Binder;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
@@ -35,8 +36,10 @@ import android.util.Pair;
import android.util.TypedXmlPullParser; import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer; import android.util.TypedXmlSerializer;
import com.android.internal.util.FunctionalUtils;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSetting; import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.Settings;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -46,6 +49,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
public interface DomainVerificationManagerInternal { public interface DomainVerificationManagerInternal {
@@ -108,9 +112,10 @@ public interface DomainVerificationManagerInternal {
int APPROVAL_LEVEL_INSTANT_APP = 5; int APPROVAL_LEVEL_INSTANT_APP = 5;
/** /**
* Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)} * Defines the possible values for
* which sorts packages by approval priority. A higher numerical value means the package should * {@link #approvalLevelForDomain(PackageSetting, Intent, List, int, int)} which sorts packages
* override all lower values. This means that comparison using less/greater than IS valid. * by approval priority. A higher numerical value means the package should override all lower
* values. This means that comparison using less/greater than IS valid.
* *
* Negative values are possible, although not implemented, reserved if explicit disable of a * Negative values are possible, although not implemented, reserved if explicit disable of a
* package for a domain needs to be tracked. * package for a domain needs to be tracked.
@@ -184,7 +189,7 @@ public interface DomainVerificationManagerInternal {
/** /**
* Migrates verification state from a previous install to a new one. It is expected that the * Migrates verification state from a previous install to a new one. It is expected that the
* {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
* {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS} * {@link #generateNewId()}. This will preserve {@link DomainVerificationState#STATE_SUCCESS}
* domains under the assumption that the new package will pass the same server side config as * domains under the assumption that the new package will pass the same server side config as
* the previous package, as they have matching signatures. * the previous package, as they have matching signatures.
* <p> * <p>
@@ -227,6 +232,11 @@ public interface DomainVerificationManagerInternal {
*/ */
void clearPackage(@NonNull String packageName); void clearPackage(@NonNull String packageName);
/**
* Remove all state for the given package for the given user.
*/
void clearPackageForUser(@NonNull String packageName, @UserIdInt int userId);
/** /**
* Delete all the state for a user. This can be because the user has been removed from the * Delete all the state for a user. This can be because the user has been removed from the
* device, or simply that the state for a user should be deleted. * device, or simply that the state for a user should be deleted.
@@ -271,7 +281,7 @@ public interface DomainVerificationManagerInternal {
/** /**
* Until the legacy APIs are entirely removed, returns the legacy state from the previously * Until the legacy APIs are entirely removed, returns the legacy state from the previously
* written info stored in {@link com.android.server.pm.Settings}. * written info stored in {@link Settings}.
*/ */
int getLegacyState(@NonNull String packageName, @UserIdInt int userId); int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
@@ -283,15 +293,12 @@ public interface DomainVerificationManagerInternal {
* @param userId the specific user to print, or null to skip printing user selection * @param userId the specific user to print, or null to skip printing user selection
* states, supports {@link android.os.UserHandle#USER_ALL} * states, supports {@link android.os.UserHandle#USER_ALL}
* @param pkgSettingFunction the method by which to retrieve package data; if this is called * @param pkgSettingFunction the method by which to retrieve package data; if this is called
* from {@link com.android.server.pm.PackageManagerService}, it is * from {@link PackageManagerService}, it is
* expected to pass in the snapshot of {@link PackageSetting} objects, * expected to pass in the snapshot of {@link PackageSetting} objects
* or if null is passed, the manager may decide to lock {@link
* com.android.server.pm.PackageManagerService} through {@link
* Connection#getPackageSettingLocked(String)}
*/ */
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId, @Nullable @UserIdInt Integer userId,
@Nullable Function<String, PackageSetting> pkgSettingFunction) @NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException; throws NameNotFoundException;
@NonNull @NonNull
@@ -363,17 +370,46 @@ public interface DomainVerificationManagerInternal {
*/ */
void schedule(int code, @Nullable Object object); void schedule(int code, @Nullable Object object);
// TODO(b/178733426): Make DomainVerificationService PMS snapshot aware so it can avoid /**
// locking package state at all. This can be as simple as removing this method in favor of * Run a function block that requires access to {@link PackageSetting} data. This will
// accepting a PackageSetting function in at every method call, although should probably * ensure the {@link PackageManagerService} is taken before
// be abstracted to a wrapper class. * {@link DomainVerificationManagerInternal}'s lock is taken to avoid deadlock.
@Nullable */
PackageSetting getPackageSettingLocked(@NonNull String pkgName); void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block);
@Nullable /**
AndroidPackage getPackageLocked(@NonNull String pkgName); * Variant which returns a value to the caller.
* @see #withPackageSettings(Consumer)
*/
<Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block);
/**
* Variant which throws.
* @see #withPackageSettings(Consumer)
*/
<ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType;
/**
* Variant which returns a value to the caller and throws.
* @see #withPackageSettings(Consumer)
*/
<Output, ExceptionType extends Exception> Output withPackageSettingsReturningThrowing(
@NonNull ThrowingFunction<Function<String, PackageSetting>, Output, ExceptionType>
block) throws ExceptionType;
@UserIdInt @UserIdInt
int[] getAllUserIds(); int[] getAllUserIds();
interface ThrowingConsumer<Input, ExceptionType extends Exception> {
void accept(Input input) throws ExceptionType;
}
interface ThrowingFunction<Input, Output, ExceptionType extends Exception> {
Output apply(Input input) throws ExceptionType;
}
} }
} }

View File

@@ -18,10 +18,12 @@ package com.android.server.pm.verify.domain;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState; import android.content.pm.verify.domain.DomainVerificationState;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.TypedXmlPullParser; import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer; import android.util.TypedXmlSerializer;
@@ -36,6 +38,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
public class DomainVerificationPersistence { public class DomainVerificationPersistence {
@@ -49,6 +52,7 @@ public class DomainVerificationPersistence {
private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_ID = "id"; private static final String ATTR_ID = "id";
private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains"; private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
private static final String ATTR_SIGNATURE = "signature";
private static final String TAG_USER_STATES = "user-states"; private static final String TAG_USER_STATES = "user-states";
public static final String TAG_USER_STATE = "user-state"; public static final String TAG_USER_STATE = "user-state";
@@ -62,10 +66,18 @@ public class DomainVerificationPersistence {
public static final String ATTR_NAME = "name"; public static final String ATTR_NAME = "name";
public static final String ATTR_STATE = "state"; public static final String ATTR_STATE = "state";
/**
* @param pkgNameToSignature Converts package name to a string representation of its signature.
* Usually this is the SHA-256 hash from
* {@link PackageUtils#computeSignaturesSha256Digest(Signature[])},
* but can be an arbitrary string for testing purposes. Pass non-null
* to write out signatures, or null to ignore.
*/
public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer, public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
@NonNull ArrayMap<String, DomainVerificationPkgState> pending, @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
@NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException { @NonNull ArrayMap<String, DomainVerificationPkgState> restored,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) { try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
try (SettingsXml.WriteSection ignored = serializer.startSection( try (SettingsXml.WriteSection ignored = serializer.startSection(
TAG_DOMAIN_VERIFICATIONS)) { TAG_DOMAIN_VERIFICATIONS)) {
@@ -86,25 +98,26 @@ public class DomainVerificationPersistence {
} }
try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) { try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
writePackageStates(activeSection, active); writePackageStates(activeSection, active, pkgNameToSignature);
} }
try (SettingsXml.WriteSection restoredSection = serializer.startSection( try (SettingsXml.WriteSection restoredSection = serializer.startSection(
TAG_RESTORED)) { TAG_RESTORED)) {
writePackageStates(restoredSection, restored.values()); writePackageStates(restoredSection, restored.values(), pkgNameToSignature);
} }
} }
} }
} }
private static void writePackageStates(@NonNull SettingsXml.WriteSection section, private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
@NonNull Collection<DomainVerificationPkgState> states) throws IOException { @NonNull Collection<DomainVerificationPkgState> states,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
if (states.isEmpty()) { if (states.isEmpty()) {
return; return;
} }
for (DomainVerificationPkgState state : states) { for (DomainVerificationPkgState state : states) {
writePkgStateToXml(section, state); writePkgStateToXml(section, state, pkgNameToSignature);
} }
} }
@@ -146,11 +159,12 @@ public class DomainVerificationPersistence {
* been entered. * been entered.
*/ */
@Nullable @Nullable
public static DomainVerificationPkgState createPkgStateFromXml( private static DomainVerificationPkgState createPkgStateFromXml(
@NonNull SettingsXml.ReadSection section) { @NonNull SettingsXml.ReadSection section) {
String packageName = section.getString(ATTR_PACKAGE_NAME); String packageName = section.getString(ATTR_PACKAGE_NAME);
String idString = section.getString(ATTR_ID); String idString = section.getString(ATTR_ID);
boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS); boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
String signature = section.getString(ATTR_SIGNATURE);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) { if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
return null; return null;
} }
@@ -172,7 +186,7 @@ public class DomainVerificationPersistence {
} }
return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap, return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
userStates); userStates, signature);
} }
private static void readUserStates(@NonNull SettingsXml.ReadSection section, private static void readUserStates(@NonNull SettingsXml.ReadSection section,
@@ -196,14 +210,26 @@ public class DomainVerificationPersistence {
} }
} }
public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection, private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationPkgState pkgState) throws IOException { @NonNull DomainVerificationPkgState pkgState,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
String packageName = pkgState.getPackageName();
String signature = pkgNameToSignature == null
? null : pkgNameToSignature.apply(packageName);
if (signature == null) {
// If a package isn't available to get its signature, fallback to the previously stored
// result, which can occur if the package has been marked for restore but hasn't
// been installed on the new device yet.
signature = pkgState.getBackupSignatureHash();
}
try (SettingsXml.WriteSection ignored = try (SettingsXml.WriteSection ignored =
parentSection.startSection(TAG_PACKAGE_STATE) parentSection.startSection(TAG_PACKAGE_STATE)
.attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName()) .attribute(ATTR_PACKAGE_NAME, packageName)
.attribute(ATTR_ID, pkgState.getId().toString()) .attribute(ATTR_ID, pkgState.getId().toString())
.attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS, .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
pkgState.isHasAutoVerifyDomains())) { pkgState.isHasAutoVerifyDomains())
.attribute(ATTR_SIGNATURE, signature)) {
writeStateMap(parentSection, pkgState.getStateMap()); writeStateMap(parentSection, pkgState.getStateMap());
writeUserStates(parentSection, pkgState.getUserStates()); writeUserStates(parentSection, pkgState.getUserStates());
} }
@@ -245,7 +271,7 @@ public class DomainVerificationPersistence {
* entered. * entered.
*/ */
@Nullable @Nullable
public static DomainVerificationInternalUserState createUserStateFromXml( private static DomainVerificationInternalUserState createUserStateFromXml(
@NonNull SettingsXml.ReadSection section) { @NonNull SettingsXml.ReadSection section) {
int userId = section.getInt(ATTR_USER_ID); int userId = section.getInt(ATTR_USER_ID);
if (userId == -1) { if (userId == -1) {
@@ -274,7 +300,7 @@ public class DomainVerificationPersistence {
} }
} }
public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection, private static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationInternalUserState userState) throws IOException { @NonNull DomainVerificationInternalUserState userState) throws IOException {
try (SettingsXml.WriteSection section = try (SettingsXml.WriteSection section =
parentSection.startSection(TAG_USER_STATE) parentSection.startSection(TAG_USER_STATE)

View File

@@ -36,6 +36,7 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; import java.io.IOException;
import java.util.function.Function;
class DomainVerificationSettings { class DomainVerificationSettings {
@@ -68,18 +69,19 @@ class DomainVerificationSettings {
public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer, public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState) @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
throws IOException { @NonNull Function<String, String> pkgSignatureFunction) throws IOException {
synchronized (mLock) { synchronized (mLock) {
DomainVerificationPersistence.writeToXml(xmlSerializer, liveState, DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
mPendingPkgStates, mRestoredPkgStates); mPendingPkgStates, mRestoredPkgStates, pkgSignatureFunction);
} }
} }
/** /**
* Parses a previously stored set of states and merges them with {@param liveState}, directly * Parses a previously stored set of states and merges them with {@param liveState}, directly
* mutating the values. This is intended for reading settings written by {@link * mutating the values. This is intended for reading settings written by {@link
* #writeSettings(TypedXmlSerializer, DomainVerificationStateMap)} on the same device setup. * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device
* setup.
*/ */
public void readSettings(@NonNull TypedXmlPullParser parser, public void readSettings(@NonNull TypedXmlPullParser parser,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState) @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
@@ -155,16 +157,20 @@ class DomainVerificationSettings {
// mergePkgState runs, without the merge part. // mergePkgState runs, without the merge part.
ArrayMap<String, Integer> stateMap = newState.getStateMap(); ArrayMap<String, Integer> stateMap = newState.getStateMap();
int size = stateMap.size(); int size = stateMap.size();
for (int index = 0; index < size; index++) { for (int index = size - 1; index >= 0; index--) {
Integer stateInteger = stateMap.valueAt(index); Integer stateInteger = stateMap.valueAt(index);
if (stateInteger != null) { if (stateInteger != null) {
int state = stateInteger; int state = stateInteger;
if (state == DomainVerificationState.STATE_SUCCESS if (state == DomainVerificationState.STATE_SUCCESS
|| state == DomainVerificationState.STATE_RESTORED) { || state == DomainVerificationState.STATE_RESTORED) {
stateMap.setValueAt(index, state); stateMap.setValueAt(index, DomainVerificationState.STATE_RESTORED);
} else {
stateMap.removeAt(index);
} }
} }
} }
mRestoredPkgStates.put(pkgName, newState);
} }
} }
} }
@@ -241,17 +247,41 @@ class DomainVerificationSettings {
} }
} }
public void removeUser(@UserIdInt int userId) { public void removePackage(@NonNull String packageName) {
int pendingSize = mPendingPkgStates.size(); synchronized (mLock) {
for (int index = 0; index < pendingSize; index++) { mPendingPkgStates.remove(packageName);
mPendingPkgStates.valueAt(index).removeUser(userId); mRestoredPkgStates.remove(packageName);
} }
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case public void removePackageForUser(@NonNull String packageName, @UserIdInt int userId) {
// on a new device synchronized (mLock) {
int restoredSize = mRestoredPkgStates.size(); final DomainVerificationPkgState pendingPkgState = mPendingPkgStates.get(packageName);
for (int index = 0; index < restoredSize; index++) { if (pendingPkgState != null) {
mRestoredPkgStates.valueAt(index).removeUser(userId); pendingPkgState.removeUser(userId);
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
// on a new device
final DomainVerificationPkgState restoredPkgState = mRestoredPkgStates.get(packageName);
if (restoredPkgState != null) {
restoredPkgState.removeUser(userId);
}
}
}
public void removeUser(@UserIdInt int userId) {
synchronized (mLock) {
int pendingSize = mPendingPkgStates.size();
for (int index = 0; index < pendingSize; index++) {
mPendingPkgStates.valueAt(index).removeUser(userId);
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
// on a new device
int restoredSize = mRestoredPkgStates.size();
for (int index = 0; index < restoredSize; index++) {
mRestoredPkgStates.valueAt(index).removeUser(userId);
}
} }
} }

View File

@@ -98,7 +98,7 @@ public class DomainVerificationShell {
pw.println(" must be declared by the package for this to work. This command will not"); pw.println(" must be declared by the package for this to work. This command will not");
pw.println(" report a failure for domains that could not be applied."); pw.println(" report a failure for domains that could not be applied.");
pw.println(" --user <USER_ID>: the user to change selections for"); pw.println(" --user <USER_ID>: the user to change selections for");
pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages"); pw.println(" --package <PACKAGE>: the package to set");
pw.println(" <ENABLED>: whether or not to approve the domain"); pw.println(" <ENABLED>: whether or not to approve the domain");
pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to"); pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
pw.println(" change every domain."); pw.println(" change every domain.");
@@ -226,8 +226,6 @@ public class DomainVerificationShell {
if (TextUtils.isEmpty(packageName)) { if (TextUtils.isEmpty(packageName)) {
commandHandler.getErrPrintWriter().println("Error: no package specified"); commandHandler.getErrPrintWriter().println("Error: no package specified");
return false; return false;
} else if (packageName.equalsIgnoreCase("all")) {
packageName = null;
} }
if (userId == null) { if (userId == null) {
@@ -478,13 +476,15 @@ public class DomainVerificationShell {
* Variant for use by PackageManagerShellCommand to allow the system/developer to override * Variant for use by PackageManagerShellCommand to allow the system/developer to override
* the state for a domain. * the state for a domain.
* *
* @param packageName the package whose state to change, or all packages if non is * If an approval fails because of a higher level owner, this method will silently skip the
* specified * domain.
*
* @param packageName the package whose state to change
* @param enabled whether the domain is now approved by the user * @param enabled whether the domain is now approved by the user
* @param domains the set of domains to change, or null to affect all domains * @param domains the set of domains to change, or null to affect all domains
*/ */
void setDomainVerificationUserSelectionInternal(@UserIdInt int userId, void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
@Nullable String packageName, boolean enabled, @Nullable ArraySet<String> domains) @NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains)
throws PackageManager.NameNotFoundException; throws PackageManager.NameNotFoundException;
/** /**

View File

@@ -7,6 +7,12 @@
"include-filter": "com.android.server.pm.test.verify.domain" "include-filter": "com.android.server.pm.test.verify.domain"
} }
] ]
},
{
"name": "CtsDomainVerificationDeviceTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
} }
] ]
} }

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain.models;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState; import android.content.pm.verify.domain.DomainVerificationState;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.SparseArray; import android.util.SparseArray;
@@ -63,15 +64,28 @@ public class DomainVerificationPkgState {
@NonNull @NonNull
private final SparseArray<DomainVerificationInternalUserState> mUserStates; private final SparseArray<DomainVerificationInternalUserState> mUserStates;
/**
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*
* @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
*/
@Nullable
private final String mBackupSignatureHash;
public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id, public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
boolean hasAutoVerifyDomains) { boolean hasAutoVerifyDomains) {
this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0)); this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0), null);
} }
public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState, public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
@NonNull UUID id, boolean hasAutoVerifyDomains) { @NonNull UUID id, boolean hasAutoVerifyDomains) {
this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(), this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
pkgState.getUserStates()); pkgState.getUserStates(), null);
} }
@Nullable @Nullable
@@ -109,7 +123,7 @@ public class DomainVerificationPkgState {
// Code below generated by codegen v1.0.22. // Code below generated by codegen v1.0.23.
// //
// DO NOT MODIFY! // DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code // CHECKSTYLE:OFF Generated code
@@ -137,6 +151,13 @@ public class DomainVerificationPkgState {
* *
* TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
* such as storing no state when the package is marked as a linked app in SystemConfig. * such as storing no state when the package is marked as a linked app in SystemConfig.
* @param backupSignatureHash
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*/ */
@DataClass.Generated.Member @DataClass.Generated.Member
public DomainVerificationPkgState( public DomainVerificationPkgState(
@@ -144,7 +165,8 @@ public class DomainVerificationPkgState {
@NonNull UUID id, @NonNull UUID id,
boolean hasAutoVerifyDomains, boolean hasAutoVerifyDomains,
@NonNull ArrayMap<String,Integer> stateMap, @NonNull ArrayMap<String,Integer> stateMap,
@NonNull SparseArray<DomainVerificationInternalUserState> userStates) { @NonNull SparseArray<DomainVerificationInternalUserState> userStates,
@Nullable String backupSignatureHash) {
this.mPackageName = packageName; this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate( com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName); NonNull.class, null, mPackageName);
@@ -158,6 +180,7 @@ public class DomainVerificationPkgState {
this.mUserStates = userStates; this.mUserStates = userStates;
com.android.internal.util.AnnotationValidations.validate( com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mUserStates); NonNull.class, null, mUserStates);
this.mBackupSignatureHash = backupSignatureHash;
// onConstructed(); // You can define this method to get a callback // onConstructed(); // You can define this method to get a callback
} }
@@ -201,6 +224,21 @@ public class DomainVerificationPkgState {
return mUserStates; return mUserStates;
} }
/**
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*
* @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
*/
@DataClass.Generated.Member
public @Nullable String getBackupSignatureHash() {
return mBackupSignatureHash;
}
@Override @Override
@DataClass.Generated.Member @DataClass.Generated.Member
public String toString() { public String toString() {
@@ -212,7 +250,8 @@ public class DomainVerificationPkgState {
"id = " + mId + ", " + "id = " + mId + ", " +
"hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " + "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
"stateMap = " + mStateMap + ", " + "stateMap = " + mStateMap + ", " +
"userStates = " + mUserStates + "userStates = " + mUserStates + ", " +
"backupSignatureHash = " + mBackupSignatureHash +
" }"; " }";
} }
@@ -233,7 +272,8 @@ public class DomainVerificationPkgState {
&& Objects.equals(mId, that.mId) && Objects.equals(mId, that.mId)
&& mHasAutoVerifyDomains == that.mHasAutoVerifyDomains && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
&& Objects.equals(mStateMap, that.mStateMap) && Objects.equals(mStateMap, that.mStateMap)
&& userStatesEquals(that.mUserStates); && userStatesEquals(that.mUserStates)
&& Objects.equals(mBackupSignatureHash, that.mBackupSignatureHash);
} }
@Override @Override
@@ -248,14 +288,15 @@ public class DomainVerificationPkgState {
_hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains); _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
_hash = 31 * _hash + Objects.hashCode(mStateMap); _hash = 31 * _hash + Objects.hashCode(mStateMap);
_hash = 31 * _hash + userStatesHashCode(); _hash = 31 * _hash + userStatesHashCode();
_hash = 31 * _hash + Objects.hashCode(mBackupSignatureHash);
return _hash; return _hash;
} }
@DataClass.Generated( @DataClass.Generated(
time = 1614818362549L, time = 1617315369614L,
codegenVersion = "1.0.22", codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mBackupSignatureHash\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated @Deprecated
private void __metadata() {} private void __metadata() {}

View File

@@ -31,6 +31,7 @@ import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationEnforcer import com.android.server.pm.verify.domain.DomainVerificationEnforcer
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.DomainVerificationService
@@ -98,10 +99,13 @@ class DomainVerificationEnforcerTest {
mockThrowOnUnmocked { mockThrowOnUnmocked {
whenever(callingUid) { callingUidInt.get() } whenever(callingUid) { callingUidInt.get() }
whenever(callingUserId) { callingUserIdInt.get() } whenever(callingUserId) { callingUserIdInt.get() }
whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } mockPackageSettings {
whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } when (it) {
whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } VISIBLE_PKG -> visiblePkgSetting
whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } INVISIBLE_PKG -> invisiblePkgSetting
else -> null
}
}
whenever(schedule(anyInt(), any())) whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings()) whenever(scheduleWriteSettings())
whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
@@ -290,6 +294,7 @@ class DomainVerificationEnforcerTest {
fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> { fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> {
whenever(this.packageName) { packageName } whenever(this.packageName) { packageName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
whenever(activities) { whenever(activities) {
listOf( listOf(
ParsedActivity().apply { ParsedActivity().apply {
@@ -330,11 +335,8 @@ class DomainVerificationEnforcerTest {
whenever(getName()) { packageName } whenever(getName()) { packageName }
whenever(getPkg()) { mockPkg(packageName) } whenever(getPkg()) { mockPkg(packageName) }
whenever(this.domainSetId) { domainSetId } whenever(this.domainSetId) { domainSetId }
whenever(userState) { whenever(readUserState(0)) { PackageUserState() }
SparseArray<PackageUserState>().apply { whenever(readUserState(1)) { PackageUserState() }
this[0] = PackageUserState()
}
}
whenever(getInstantApp(anyInt())) { false } whenever(getInstantApp(anyInt())) { false }
} }
} }

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationInfo
@@ -31,6 +32,7 @@ import android.os.Process
import android.util.ArraySet import android.util.ArraySet
import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever import com.android.server.testutils.whenever
@@ -41,6 +43,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString
import java.util.UUID import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class DomainVerificationManagerApiTest { class DomainVerificationManagerApiTest {
@@ -267,7 +270,16 @@ class DomainVerificationManagerApiTest {
@Test @Test
fun getOwnersForDomain() { fun getOwnersForDomain() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) val pkg1User0Enabled = AtomicBoolean(true)
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2), pkgUserState0 = {
mockThrowOnUnmocked {
whenever(isPackageEnabled(any())) {
pkg1User0Enabled.get()
}
installed = true
}
})
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
val service = makeService(pkg1, pkg2).apply { val service = makeService(pkg1, pkg2).apply {
@@ -276,9 +288,20 @@ class DomainVerificationManagerApiTest {
assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty() assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) assertThat(
service.setStatus(
service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) pkg1.domainSetId,
setOf(DOMAIN_1),
DomainVerificationInfo.STATE_SUCCESS
)
).isEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(
service.setStatus(
pkg2.domainSetId,
setOf(DOMAIN_1),
DomainVerificationInfo.STATE_SUCCESS
)
).isEqualTo(DomainVerificationManager.STATUS_OK)
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0) service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
@@ -295,15 +318,65 @@ class DomainVerificationManagerApiTest {
assertThat(it.single().packageName).isEqualTo(pkg1.getName()) assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true) assertThat(it.single().isOverrideable).isEqualTo(true)
} }
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 1)
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// "Uninstall" the package from user 0 and ensure it's stripped from the results
pkg1User0Enabled.set(false)
service.clearPackageForUser(pkg1.getName(), 0)
service.getOwnersForDomain(DOMAIN_1, 0).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg2.getName())
assertThat(it.single().isOverrideable).isEqualTo(false)
}
// Domain 2 user selection gone for user 0
assertThat(service.getOwnersForDomain(DOMAIN_2, 0)).isEmpty()
// Domain 2 user selection still around for user 1
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// Now assert for user 1 that it was unaffected by the change to user 0
service.getOwnersForDomain(DOMAIN_1, 1).let {
assertThat(it).hasSize(2)
assertThat(it[0].packageName).isEqualTo(pkg1.getName())
assertThat(it[0].isOverrideable).isEqualTo(false)
assertThat(it[1].packageName).isEqualTo(pkg2.getName())
assertThat(it[1].isOverrideable).isEqualTo(false)
}
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// "Reinstall" the package to user 0
pkg1User0Enabled.set(false)
// This state should have been cleared when the package was uninstalled
assertThat(service.getOwnersForDomain(DOMAIN_2, 0)).isEmpty()
// Other package unaffected
service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0) service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_2, 0).let { service.getOwnersForDomain(DOMAIN_2, 0).let {
assertThat(it).hasSize(1) assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg2.getName()) assertThat(it.single().packageName).isEqualTo(pkg2.getName())
assertThat(it.single().isOverrideable).isEqualTo(true) assertThat(it.single().isOverrideable).isEqualTo(true)
} }
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
} }
@Test @Test
@@ -373,21 +446,23 @@ class DomainVerificationManagerApiTest {
whenever(callingUid) { Process.ROOT_UID } whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 } whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) { mockPackageSettings {
pkgSettingFunction(arguments[0] as String) pkgSettingFunction(it)
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)?.getPkg()
} }
}) })
} }
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( private fun mockPkgSetting(
DOMAIN_1, DOMAIN_2 pkgName: String,
)) = mockThrowOnUnmocked<PackageSetting> { domainSetId: UUID,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
pkgUserState0: PackageSetting.() -> PackageUserState = { PackageUserState() },
pkgUserState1: PackageSetting.() -> PackageUserState = { PackageUserState() }
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> { val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName } whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf( val activityList = listOf(
ParsedActivity().apply { ParsedActivity().apply {
@@ -416,6 +491,8 @@ class DomainVerificationManagerApiTest {
whenever(this.domainSetId) { domainSetId } whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false } whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L } whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { pkgUserState0() }
whenever(readUserState(1)) { pkgUserState1() }
} }
fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) = fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =

View File

@@ -18,8 +18,11 @@ package com.android.server.pm.test.verify.domain
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.Signature
import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE
@@ -35,12 +38,12 @@ import android.util.ArraySet
import android.util.Xml import android.util.Xml
import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyLong
@@ -54,6 +57,10 @@ class DomainVerificationPackageTest {
private const val PKG_TWO = "com.test.two" private const val PKG_TWO = "com.test.two"
private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private const val SIGNATURE_ONE = "AA"
private const val DIGEST_ONE =
"BCEEF655B5A034911F1C3718CE056531B45EF03B4C7B1F15629E867294011A7D"
private const val SIGNATURE_TWO = "BB"
private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName
private val DOMAIN_1 = "one.$DOMAIN_BASE" private val DOMAIN_1 = "one.$DOMAIN_BASE"
@@ -64,8 +71,8 @@ class DomainVerificationPackageTest {
private const val USER_ID = 0 private const val USER_ID = 0
} }
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO)
@Test @Test
fun addPackageFirstTime() { fun addPackageFirstTime() {
@@ -93,6 +100,104 @@ class DomainVerificationPackageTest {
.containsExactly(pkg1.getName()) .containsExactly(pkg1.getName())
} }
@Test
fun addPackageRestoredMatchingSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="$DIGEST_ONE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageRestoredMismatchSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="INVALID_SIGNATURE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test @Test
fun addPackageActive() { fun addPackageActive() {
// language=XML // language=XML
@@ -151,10 +256,9 @@ class DomainVerificationPackageTest {
@Test @Test
fun migratePackageDropDomain() { fun migratePackageDropDomain() {
val pkgName = PKG_ONE val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4)) listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
listOf(DOMAIN_1, DOMAIN_2))
// Test 4 domains: // Test 4 domains:
// 1 will be approved and preserved, 2 will be selected and preserved, // 1 will be approved and preserved, 2 will be selected and preserved,
@@ -213,8 +317,8 @@ class DomainVerificationPackageTest {
@Test @Test
fun migratePackageDropAll() { fun migratePackageDropAll() {
val pkgName = PKG_ONE val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList()) val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, emptyList())
val map = mutableMapOf<String, PackageSetting>() val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] } val service = makeService { map[it] }
@@ -255,10 +359,9 @@ class DomainVerificationPackageTest {
@Test @Test
fun migratePackageAddDomain() { fun migratePackageAddDomain() {
val pkgName = PKG_ONE val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
listOf(DOMAIN_1, DOMAIN_2)) val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO,
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
// Test 3 domains: // Test 3 domains:
// 1 will be verified and preserved, 2 will be selected and preserved, // 1 will be verified and preserved, 2 will be selected and preserved,
@@ -307,8 +410,8 @@ class DomainVerificationPackageTest {
@Test @Test
fun migratePackageAddAll() { fun migratePackageAddAll() {
val pkgName = PKG_ONE val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList()) val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, emptyList())
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
val map = mutableMapOf<String, PackageSetting>() val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] } val service = makeService { map[it] }
@@ -379,21 +482,22 @@ class DomainVerificationPackageTest {
whenever(callingUid) { Process.ROOT_UID } whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 } whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) { mockPackageSettings {
pkgSettingFunction(arguments[0] as String)!! pkgSettingFunction(it)
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!.getPkg()
} }
}) })
} }
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( private fun mockPkgSetting(
DOMAIN_1, DOMAIN_2 pkgName: String,
)) = mockThrowOnUnmocked<PackageSetting> { domainSetId: UUID,
signature: String,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2)
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> { val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName } whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf( val activityList = listOf(
ParsedActivity().apply { ParsedActivity().apply {
@@ -422,5 +526,7 @@ class DomainVerificationPackageTest {
whenever(this.domainSetId) { domainSetId } whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false } whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L } whenever(firstInstallTime) { 0L }
whenever(readUserState(USER_ID)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
} }
} }

View File

@@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationState
import android.util.ArrayMap import android.util.ArrayMap
import android.util.SparseArray
import android.util.TypedXmlPullParser import android.util.TypedXmlPullParser
import android.util.TypedXmlSerializer import android.util.TypedXmlSerializer
import android.util.Xml import android.util.Xml
@@ -84,23 +85,32 @@ class DomainVerificationPersistenceTest {
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
@Test private fun mockWriteValues(
fun writeAndReadBackNormal() { pkgNameToSignature: (String) -> String? = { null }
): Triple<DomainVerificationStateMap<DomainVerificationPkgState>,
ArrayMap<String, DomainVerificationPkgState>,
ArrayMap<String, DomainVerificationPkgState>> {
val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply { val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
mockPkgState(0).let { put(it.packageName, it.id, it) } mockPkgState(0, pkgNameToSignature).let { put(it.packageName, it.id, it) }
mockPkgState(1).let { put(it.packageName, it.id, it) } mockPkgState(1, pkgNameToSignature).let { put(it.packageName, it.id, it) }
} }
val pending = ArrayMap<String, DomainVerificationPkgState>().apply { val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
mockPkgState(2).let { put(it.packageName, it) } mockPkgState(2, pkgNameToSignature).let { put(it.packageName, it) }
mockPkgState(3).let { put(it.packageName, it) } mockPkgState(3, pkgNameToSignature).let { put(it.packageName, it) }
} }
val restored = ArrayMap<String, DomainVerificationPkgState>().apply { val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
mockPkgState(4).let { put(it.packageName, it) } mockPkgState(4, pkgNameToSignature).let { put(it.packageName, it) }
mockPkgState(5).let { put(it.packageName, it) } mockPkgState(5, pkgNameToSignature).let { put(it.packageName, it) }
} }
return Triple(attached, pending, restored)
}
@Test
fun writeAndReadBackNormal() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml { val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null)
} }
val xml = file.readText() val xml = file.readText()
@@ -114,9 +124,57 @@ class DomainVerificationPersistenceTest {
assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values) assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
} }
@Test
fun writeAndReadBackWithSignature() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) {
"SIGNATURE_$it"
}
}
val (readActive, readRestored) = file.readXml {
DomainVerificationPersistence.readFromXml(it)
}
// Assign the signatures to a fresh set of data structures, to ensure the previous write
// call did not use the signatures from the data structure. This is because the method is
// intended to optionally append signatures, regardless of if the existing data structures
// contain them or not.
val (attached2, pending2, restored2) = mockWriteValues { "SIGNATURE_$it" }
assertThat(readActive.values)
.containsExactlyElementsIn(attached2.values() + pending2.values)
assertThat(readRestored.values).containsExactlyElementsIn(restored2.values)
(readActive + readRestored).forEach { (_, value) ->
assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
}
}
@Test
fun writeStateSignatureIfFunctionReturnsNull() {
val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" }
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null }
}
val (readActive, readRestored) = file.readXml {
DomainVerificationPersistence.readFromXml(it)
}
assertThat(readActive.values)
.containsExactlyElementsIn(attached.values() + pending.values)
assertThat(readRestored.values).containsExactlyElementsIn(restored.values)
(readActive + readRestored).forEach { (_, value) ->
assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
}
}
@Test @Test
fun readMalformed() { fun readMalformed() {
val stateZero = mockEmptyPkgState(0).apply { val stateZero = mockEmptyPkgState(0, pkgNameToSignature = { "ACTIVE" }).apply {
stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS
stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED
@@ -128,7 +186,7 @@ class DomainVerificationPersistenceTest {
isLinkHandlingAllowed = true isLinkHandlingAllowed = true
} }
} }
val stateOne = mockEmptyPkgState(1).apply { val stateOne = mockEmptyPkgState(1, pkgNameToSignature = { "RESTORED" }).apply {
// It's valid to have a user selection without any autoVerify domains // It's valid to have a user selection without any autoVerify domains
userStates[1] = DomainVerificationInternalUserState(1).apply { userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org")) addHosts(setOf("example-user1.com", "example-user1.org"))
@@ -156,6 +214,7 @@ class DomainVerificationPersistenceTest {
packageName="${stateZero.packageName}" packageName="${stateZero.packageName}"
id="${stateZero.id}" id="${stateZero.id}"
hasAutoVerifyDomains="true" hasAutoVerifyDomains="true"
signature="ACTIVE"
> >
<state> <state>
<domain name="example.com" state="${ <domain name="example.com" state="${
@@ -191,6 +250,7 @@ class DomainVerificationPersistenceTest {
packageName="${stateOne.packageName}" packageName="${stateOne.packageName}"
id="${stateOne.id}" id="${stateOne.id}"
hasAutoVerifyDomains="true" hasAutoVerifyDomains="true"
signature="RESTORED"
> >
<state/> <state/>
<user-states> <user-states>
@@ -226,20 +286,30 @@ class DomainVerificationPersistenceTest {
private fun mockEmptyPkgState( private fun mockEmptyPkgState(
id: Int, id: Int,
hasAutoVerifyDomains: Boolean = true hasAutoVerifyDomains: Boolean = true,
pkgNameToSignature: (String) -> String? = { null }
): DomainVerificationPkgState { ): DomainVerificationPkgState {
val pkgName = pkgName(id) val pkgName = pkgName(id)
val domainSetId = UUID(0L, id.toLong()) val domainSetId = UUID(0L, id.toLong())
return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains) return DomainVerificationPkgState(
pkgName,
domainSetId,
hasAutoVerifyDomains,
ArrayMap(),
SparseArray(),
pkgNameToSignature(pkgName)
)
} }
private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply { private fun mockPkgState(id: Int, pkgNameToSignature: (String) -> String? = { null }) =
stateMap["$packageName.com"] = id mockEmptyPkgState(id, pkgNameToSignature = pkgNameToSignature)
userStates[id] = DomainVerificationInternalUserState(id).apply { .apply {
addHosts(setOf("$packageName-user.com")) stateMap["$packageName.com"] = id
isLinkHandlingAllowed = true userStates[id] = DomainVerificationInternalUserState(id).apply {
} addHosts(setOf("$packageName-user.com"))
} isLinkHandlingAllowed = true
}
}
private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id" private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
} }

View File

@@ -29,6 +29,7 @@ import android.util.ArraySet
import android.util.SparseArray import android.util.SparseArray
import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
@@ -192,6 +193,7 @@ class DomainVerificationSettingsMutationTest {
fun mockPkg() = mockThrowOnUnmocked<AndroidPackage> { fun mockPkg() = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { TEST_PKG } whenever(packageName) { TEST_PKG }
whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
whenever(activities) { whenever(activities) {
listOf( listOf(
ParsedActivity().apply { ParsedActivity().apply {
@@ -233,11 +235,8 @@ class DomainVerificationSettingsMutationTest {
whenever(getName()) { TEST_PKG } whenever(getName()) { TEST_PKG }
whenever(getPkg()) { mockPkg() } whenever(getPkg()) { mockPkg() }
whenever(domainSetId) { TEST_UUID } whenever(domainSetId) { TEST_UUID }
whenever(userState) { whenever(readUserState(0)) { PackageUserState() }
SparseArray<PackageUserState>().apply { whenever(readUserState(10)) { PackageUserState() }
this[0] = PackageUserState()
}
}
whenever(getInstantApp(anyInt())) { false } whenever(getInstantApp(anyInt())) { false }
} }
} }
@@ -258,8 +257,12 @@ class DomainVerificationSettingsMutationTest {
mockThrowOnUnmocked { mockThrowOnUnmocked {
whenever(callingUid) { TEST_UID } whenever(callingUid) { TEST_UID }
whenever(callingUserId) { TEST_USER_ID } whenever(callingUserId) { TEST_USER_ID }
whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting() } mockPackageSettings {
whenever(getPackageLocked(TEST_PKG)) { mockPkg() } when (it) {
TEST_PKG -> mockPkgSetting()
else -> null
}
}
whenever(schedule(anyInt(), any())) whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings()) whenever(scheduleWriteSettings())

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
package com.android.server.pm.test.verify.domain
import com.android.internal.util.FunctionalUtils
import com.android.server.pm.PackageSetting
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.testutils.whenever
import org.mockito.ArgumentMatchers.any
import java.util.function.Consumer
import java.util.function.Function
internal object DomainVerificationTestUtils {
@Suppress("UNCHECKED_CAST")
fun DomainVerificationManagerInternal.Connection.mockPackageSettings(
block: (String) -> PackageSetting?
) {
whenever(withPackageSettings(any())) {
(arguments[0] as Consumer<Function<String, PackageSetting?>>).accept { block(it) }
}
whenever(withPackageSettingsReturning<Any>(any())) {
(arguments[0] as FunctionalUtils.ThrowingFunction<Function<String, PackageSetting?>, *>)
.apply { block(it) }
}
whenever(withPackageSettingsThrowing<Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingConsumer<
Function<String, PackageSetting?>, *>)
.accept { block(it) }
}
whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction<
Function<String, PackageSetting?>, *, *>)
.apply { block(it) }
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationManager
@@ -29,6 +30,7 @@ import android.os.Process
import android.util.ArraySet import android.util.ArraySet
import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever import com.android.server.testutils.whenever
@@ -82,10 +84,13 @@ class DomainVerificationUserStateOverrideTest {
// Need to provide an internal UID so some permission checks are ignored // Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID } whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 } whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 } mockPackageSettings {
whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 } when (it) {
whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() } PKG_ONE -> pkg1
whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() } PKG_TWO -> pkg2
else -> null
}
}
}) })
addPackage(pkg1) addPackage(pkg1)
addPackage(pkg2) addPackage(pkg2)
@@ -100,6 +105,7 @@ class DomainVerificationUserStateOverrideTest {
val pkg = mockThrowOnUnmocked<AndroidPackage> { val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName } whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf( val activityList = listOf(
ParsedActivity().apply { ParsedActivity().apply {
@@ -137,6 +143,8 @@ class DomainVerificationUserStateOverrideTest {
whenever(this.domainSetId) { domainSetId } whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false } whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L } whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
} }
@Test @Test