From 5333c7557a094100382f9870e986165585f5036b Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 24 Feb 2021 15:38:40 -0800 Subject: [PATCH] Expose getDomainVerificationUserSelection as public API This is now visible to all apps to get the verified status of domains which they declare. Because this is already restricted by app visibility, opening up the API with no changes should be sufficient. The permission requirement was also removed, since normals apps will not have it. This will only expose a relevant status when used with the changes at Ib14049e397154616f84a2264167ac30659cd81c9. Bug: 180955393 CTS-Coverage-Bug: 179382047 Change-Id: Ia7b7acffe41ac435f9bda98cd4e0ee422c8df328 --- core/api/current.txt | 21 ++ core/api/system-current.txt | 13 +- .../android/app/SystemServiceRegistry.java | 1 - .../domain/DomainVerificationManager.java | 188 +++++++++++------- .../DomainVerificationUserSelection.java | 57 +++--- .../domain/DomainVerificationEnforcer.java | 15 ++ .../domain/DomainVerificationService.java | 2 +- .../domain/DomainVerificationEnforcerTest.kt | 72 ++++++- 8 files changed, 251 insertions(+), 118 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 61d30de25147b..8ce2d5d0da05d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12947,6 +12947,27 @@ package android.content.pm { } +package android.content.pm.verify.domain { + + public final class DomainVerificationManager { + method @Nullable public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + } + + public final class DomainVerificationUserSelection implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map getHostToStateMap(); + method @NonNull public String getPackageName(); + method @NonNull public android.os.UserHandle getUser(); + method @NonNull public boolean isLinkHandlingAllowed(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int DOMAIN_STATE_NONE = 0; // 0x0 + field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 + field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 + } + +} + package android.content.res { public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c85f566344258..af16151d5e7b4 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2771,8 +2771,7 @@ package android.content.pm.verify.domain { public final class DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @NonNull public java.util.List getOwnersForDomain(@NonNull String); + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List getOwnersForDomain(@NonNull String); method public static boolean isStateModifiable(int); method public static boolean isStateVerified(int); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List queryValidVerificationPackageNames(); @@ -2793,17 +2792,7 @@ package android.content.pm.verify.domain { } public final class DomainVerificationUserSelection implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map getHostToStateMap(); method @NonNull public java.util.UUID getIdentifier(); - method @NonNull public String getPackageName(); - method @NonNull public android.os.UserHandle getUser(); - method @NonNull public boolean isLinkHandlingAllowed(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int DOMAIN_STATE_NONE = 0; // 0x0 - field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 - field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index a9b5a6da98843..43c14a99b2212 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1421,7 +1421,6 @@ public final class SystemServiceRegistry { } }); - // TODO(b/159952358): Only register this service for the domain verification agent? registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index 4df8d167e7d16..e18d7a0248623 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -29,60 +29,66 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; /** - * System service to access the domain verification APIs. + * System service to access domain verification APIs. * - * Allows the approved domain verification - * agent on the device (the sole holder of - * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status - * of domains declared by applications in their AndroidManifest.xml, to allow them to open those - * links inside the app when selected by the user. This is done through querying - * {@link #getDomainVerificationInfo(String)} and calling - * {@link #setDomainVerificationStatus(UUID, Set, int)}. - * - * Also allows the domain preference settings (holder of - * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the - * preferences of the user, when they have chosen to explicitly allow an application to open links. - * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling - * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and - * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. - * - * @hide + * Applications should use {@link #getDomainVerificationUserSelection(String)} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. */ -@SystemApi @SystemService(Context.DOMAIN_VERIFICATION_SERVICE) public final class DomainVerificationManager { /** - * Extra field name for a {@link DomainVerificationRequest} for the requested packages. - * Passed to an the domain verification agent that handles + * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed + * to an the domain verification agent that handles * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}. + * + * @hide */ + @SystemApi public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; /** * No response has been recorded by either the system or any verification agent. + * + * @hide */ + @SystemApi public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; - /** The verification agent has explicitly verified the domain at some point. */ + /** + * The verification agent has explicitly verified the domain at some point. + * + * @hide + */ + @SystemApi public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; /** - * The first available custom response code. This and any greater integer, along with - * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values - * will be treated as if the domain is unverified. + * The first available custom response code. This and any greater integer, along with {@link + * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be + * treated as if the domain is unverified. + * + * @hide */ + @SystemApi public static final int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; - /** @hide */ + /** + * @hide + */ @NonNull public static String stateToDebugString(@DomainVerificationState.State int state) { switch (state) { @@ -108,9 +114,12 @@ public final class DomainVerificationManager { } /** - * Checks if a state considers the corresponding domain to be successfully verified. The - * domain verification agent may use this to determine whether or not to re-verify a domain. + * Checks if a state considers the corresponding domain to be successfully verified. The domain + * verification agent may use this to determine whether or not to re-verify a domain. + * + * @hide */ + @SystemApi public static boolean isStateVerified(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_SUCCESS: @@ -130,9 +139,12 @@ public final class DomainVerificationManager { /** * Checks if a state is modifiable by the domain verification agent. This is useful as the * platform may add new state codes in newer versions, and older verification agents can use - * this method to determine if a state can be changed without having to be aware of what the - * new state means. + * this method to determine if a state can be changed without having to be aware of what the new + * state means. + * + * @hide */ + @SystemApi public static boolean isStateModifiable(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: @@ -151,8 +163,9 @@ public final class DomainVerificationManager { } /** - * For determine re-verify policy. This is hidden from the domain verification agent so that - * no behavior is made based on the result. + * For determine re-verify policy. This is hidden from the domain verification agent so that no + * behavior is made based on the result. + * * @hide */ public static boolean isStateDefault(@DomainVerificationState.State int state) { @@ -171,13 +184,19 @@ public final class DomainVerificationManager { } } - /** @hide */ + /** + * @hide + */ public static final int ERROR_INVALID_DOMAIN_SET = 1; - /** @hide */ + /** + * @hide + */ public static final int ERROR_NAME_NOT_FOUND = 2; - /** @hide */ - @IntDef(prefix = { "ERROR_" }, value = { + /** + * @hide + */ + @IntDef(prefix = {"ERROR_"}, value = { ERROR_INVALID_DOMAIN_SET, ERROR_NAME_NOT_FOUND, }) @@ -188,7 +207,27 @@ public final class DomainVerificationManager { private final IDomainVerificationManager mDomainVerificationManager; - /** @hide */ + + /** + * System service to access the domain verification APIs. + *

+ * Allows the approved domain verification agent on the device (the sole holder of {@link + * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of + * domains declared by applications in their AndroidManifest.xml, to allow them to open those + * links inside the app when selected by the user. This is done through querying {@link + * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID, + * Set, int)}. + *

+ * Also allows the domain preference settings (holder of + * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) + * to update the preferences of the user, when they have chosen to explicitly allow an + * application to open links. This is done through querying + * {@link #getDomainVerificationUserSelection(String)} and calling + * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and + * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. + * + * @hide + */ public DomainVerificationManager(Context context, IDomainVerificationManager domainVerificationManager) { mContext = context; @@ -200,7 +239,9 @@ public final class DomainVerificationManager { * usually a heavy workload and should be done infrequently. * * @return the current snapshot of package names with valid autoVerify URLs. + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public List queryValidVerificationPackageNames() { @@ -217,7 +258,9 @@ public final class DomainVerificationManager { * @return the data for the package, or null if it does not declare any autoVerify domains * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error * and should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @Nullable @RequiresPermission(anyOf = { android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, @@ -240,25 +283,25 @@ public final class DomainVerificationManager { } /** - * Change the verification status of the {@param domains} of the package associated with - * {@param domainSetId}. + * Change the verification status of the {@param domains} of the package associated with {@param + * domainSetId}. * * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains List of host names to change the state of. * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are * invalid. This usually means the work being processed by the - * verification agent is outdated and a new request should - * be scheduled, if one has not already been done as part of - * the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} - * broadcast. + * verification agent is outdated and a new request should be + * scheduled, if one has not already been done as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is - * installed on a volume that is no longer mounted. This - * error is unrecoverable until the package is available - * again, and should not be re-tried except on a time - * scheduled basis. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set domains, @DomainVerificationState.State int state) throws NameNotFoundException { @@ -284,7 +327,10 @@ public final class DomainVerificationManager { * with this enabled is not guaranteed to be the sole link handler for its domains. *

* By default, all apps are allowed to open links. Users must disable them explicitly. + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed) throws NameNotFoundException { @@ -307,11 +353,11 @@ public final class DomainVerificationManager { * Update the recorded user selection for the given {@param domains} for the given {@param * domainSetId}. This state is recorded for the lifetime of a domain for a package on device, * and will never be reset by the system short of an app data clear. - * + *

* This state is stored per device user. If another user needs to be changed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. - * + * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should + * be used. + *

* Enabling an unverified domain will allow an application to open it, but this can only occur * if no other app on the device is approved for a higher approval level. This can queried * using {@link #getOwnersForDomain(String)}. @@ -329,12 +375,13 @@ public final class DomainVerificationManager { * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are * invalid. * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is - * installed on a volume that is no longer mounted. This - * error is unrecoverable until the package is available - * again, and should not be re-tried except on a time - * scheduled basis. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set domains, boolean enabled) throws NameNotFoundException { @@ -355,19 +402,12 @@ public final class DomainVerificationManager { /** * Retrieve the user selection data for the given {@param packageName} and the current user. - * It is the responsibility of the caller to ensure that the - * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls. - * - * This state is stored per device user. If another user needs to be accessed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. * * @param packageName The app to query state for. - * @return the user selection verification data for the given package for the current user, - * or null if the package does not declare any HTTP/HTTPS domains. + * @return the user selection verification data for the given package for the current user, or + * null if the package does not declare any HTTP/HTTPS domains. */ @Nullable - @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public DomainVerificationUserSelection getDomainVerificationUserSelection( @NonNull String packageName) throws NameNotFoundException { try { @@ -393,7 +433,10 @@ public final class DomainVerificationManager { * * By default the list will be returned ordered from lowest to highest * priority. + * + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public List getOwnersForDomain(@NonNull String domain) { @@ -442,8 +485,9 @@ public final class DomainVerificationManager { * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains * provided by the caller is no longer valid. This may be recoverable, and the caller should * re-query the package name associated with the ID using - * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the - * package is no longer known to the device and thus all pending work for it should be dropped. + * {@link #getDomainVerificationInfo(String)} + * in order to check. If that also fails, then the package is no longer known to the device and + * thus all pending work for it should be dropped. * * @hide */ @@ -455,7 +499,9 @@ public final class DomainVerificationManager { public static final int REASON_UNKNOWN_DOMAIN = 4; public static final int REASON_UNABLE_TO_APPROVE = 5; - /** @hide */ + /** + * @hide + */ @IntDef({ REASON_ID_NULL, REASON_ID_INVALID, @@ -494,7 +540,9 @@ public final class DomainVerificationManager { @Nullable private final String mPackageName; - /** @hide */ + /** + * @hide + */ public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, @Reason int reason) { super(buildMessage(domainSetId, packageName, reason)); diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java index d23f5f133841e..8b7f1467a0cb4 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -36,28 +37,26 @@ import java.util.UUID; * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a * package in its manifest, whether or not they were marked for auto verification. *

- * By default, all apps are allowed to automatically open links with domains that they've - * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user - * can decide to disable this, disallowing the application from opening all links. Note that the - * toggle affects all links and is not based on the verification state of the domains. + * Applications should use {@link #getHostToStateMap()} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. + *

+ * By default, all apps are allowed to automatically open links for the above case for domains that + * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. + * The user can decide to disable this, disallowing the application from opening all links. Note + * that the toggle affects all links and is not based on the verification state of the + * domains. *

* Assuming the toggle is enabled, the user can also select additional unverified domains to grant * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single * application can be approved for a domain unless the applications are both approved. If another * application is approved, the user will not be allowed to enable the domain. - *

- * These values can be changed through the - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)} APIs. - *

- * Note that because state is per user, if a different user needs to be changed, one will need to - * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link - * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. - * - * @hide */ -@SystemApi @SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true, genHiddenConstDefs = true) @@ -70,9 +69,8 @@ public final class DomainVerificationUserSelection implements Parcelable { public static final int DOMAIN_STATE_NONE = 0; /** - * The domain has been selected through the - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)} - * API, under the assumption it has not been reset by the system. + * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if + * another application is selected or verified for the same domain. */ public static final int DOMAIN_STATE_SELECTED = 1; @@ -122,6 +120,15 @@ public final class DomainVerificationUserSelection implements Parcelable { DomainVerificationUserSelection.class.getClassLoader()); } + /** + * @see DomainVerificationInfo#getIdentifier + * @hide + */ + @SystemApi + public @NonNull UUID getIdentifier() { + return mIdentifier; + } + // Code below generated by codegen v1.0.22. @@ -200,14 +207,6 @@ public final class DomainVerificationUserSelection implements Parcelable { // onConstructed(); // You can define this method to get a callback } - /** - * @see DomainVerificationInfo#getIdentifier - */ - @DataClass.Generated.Member - public @NonNull UUID getIdentifier() { - return mIdentifier; - } - /** * The package name that this data corresponds to. */ @@ -368,10 +367,10 @@ public final class DomainVerificationUserSelection implements Parcelable { }; @DataClass.Generated( - time = 1613683603297L, + time = 1614649708937L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java", - inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") + inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index ed37fa0da01f9..712ed6c19152a 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -129,6 +129,21 @@ public class DomainVerificationEnforcer { } } + /** + * Enforced when mutating user selection state inside an exposed API method. + */ + public boolean assertApprovedUserSelectionQuerent(int callingUid, @UserIdInt int callingUserId, + @NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException { + if (callingUserId != targetUserId) { + mContext.enforcePermission( + Manifest.permission.INTERACT_ACROSS_USERS, + Binder.getCallingPid(), callingUid, + "Caller is not allowed to edit other users"); + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); + } + /** * Enforced when mutating user selection state inside an exposed API method. */ diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index f5fc58ea83297..3b25c7a24eba6 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -629,7 +629,7 @@ public class DomainVerificationService extends SystemService @Override public DomainVerificationUserSelection getDomainVerificationUserSelection( @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { - if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), + if (!mEnforcer.assertApprovedUserSelectionQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), packageName, userId)) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index fa700b9a7a207..2616a59c7edba 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -154,6 +154,15 @@ class DomainVerificationEnforcerTest { enforcer(Type.VERIFIER, "approvedVerifier") { assertApprovedVerifier(it.callingUid, it.proxy) }, + enforcer( + Type.SELECTION_QUERENT, + "approvedUserSelectionQuerent" + ) { + assertApprovedUserSelectionQuerent( + it.callingUid, it.callingUserId, + it.targetPackageName, it.userId + ) + }, enforcer( Type.SELECTOR, "approvedUserSelector" @@ -211,7 +220,7 @@ class DomainVerificationEnforcerTest { service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId) }, - service(Type.SELECTOR_USER, "getUserSelectionUserId") { + service(Type.SELECTION_QUERENT, "getUserSelectionUserId") { getDomainVerificationUserSelection(it.targetPackageName, it.userId) }, service(Type.SELECTOR_USER, "setUserSelectionUserId") { @@ -345,6 +354,7 @@ class DomainVerificationEnforcerTest { Type.INTERNAL -> internal() Type.QUERENT -> approvedQuerent() Type.VERIFIER -> approvedVerifier() + Type.SELECTION_QUERENT -> approvedUserSelectionQuerent(verifyCrossUser = true) Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false) Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) Type.LEGACY_QUERENT -> legacyQuerent() @@ -354,7 +364,7 @@ class DomainVerificationEnforcerTest { }.run { /*exhaust*/ } } - fun internal() { + private fun internal() { val context: Context = mockThrowOnUnmocked() val target = params.construct(context) @@ -368,7 +378,7 @@ class DomainVerificationEnforcerTest { } } - fun approvedQuerent() { + private fun approvedQuerent() { val allowUserSelection = AtomicBoolean(false) val allowPreferredApps = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) @@ -410,7 +420,7 @@ class DomainVerificationEnforcerTest { runMethod(target, NON_VERIFIER_UID) } - fun approvedVerifier() { + private fun approvedVerifier() { val allowDomainVerificationAgent = AtomicBoolean(false) val allowIntentVerificationAgent = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) @@ -452,7 +462,56 @@ class DomainVerificationEnforcerTest { assertFails { runMethod(target, NON_VERIFIER_UID) } } - fun approvedUserSelector(verifyCrossUser: Boolean) { + private fun approvedUserSelectionQuerent(verifyCrossUser: Boolean) { + val allowInteractAcrossUsers = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // User selector makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + // User selector doesn't use QUERY_ALL, so the invisible package should always fail + allUids.forEach { + assertFails { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } + } + } + + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + + private fun approvedUserSelector(verifyCrossUser: Boolean) { val allowUserSelection = AtomicBoolean(false) val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { @@ -752,6 +811,9 @@ class DomainVerificationEnforcerTest { // INTERNAL || domain verification agent VERIFIER, + // No permissions, allows all apps to view domain state for visible packages + SELECTION_QUERENT, + // Holding the user setting permission SELECTOR,