Make visible only accounts for which authenticator supports contacts operations
for callers with READ_CONTACTS permission.
Test: manual
Bug: 36983643
Change-Id: I1239a30a71cb13ce9ffff6f38b8506e9686abe4d
(cherry picked from commit d7e7a74179c51a36845c35614ac3247c13474fca)
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 2a2fdbd..8052288 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -364,11 +364,19 @@
"android.accounts.key_legacy_visible";
/**
- * Key to set visibility for applications targeting API level below
- * {@link android.os.Build.VERSION_CODES#O} with
- * {@link android.Manifest.permission#GET_ACCOUNTS} permission, or applications with any
- * targeting API level with the same signature as authenticator. See
- * {@link #getAccountVisibility}. If the value was not set by authenticator
+ * Key to set visibility for applications which satisfy one of the following conditions:
+ * <ul>
+ * <li>Target API level below {@link android.os.Build.VERSION_CODES#O} and have
+ * deprecated {@link android.Manifest.permission#GET_ACCOUNTS} permission.
+ * </li>
+ * <li> Have {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permission. </li>
+ * <li> Have the same signature as authenticator. </li>
+ * <li> Have {@link android.Manifest.permission#READ_CONTACTS} permission and
+ * account type may be associated with contacts data - (verified by
+ * {@link android.Manifest.permission#WRITE_CONTACTS} permission check for the authenticator).
+ * </li>
+ * </ul>
+ * See {@link #getAccountVisibility}. If the value was not set by authenticator
* {@link #VISIBILITY_USER_MANAGED_VISIBLE} is used.
*/
public static final String PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE =
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 738365d..5f585cc 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -652,20 +652,20 @@
return visibility;
}
- boolean isPrivileged = isPermittedForPackage(packageName, accounts.userId,
+ boolean isPrivileged = isPermittedForPackage(packageName, uid, accounts.userId,
Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
// Device/Profile owner gets visibility by default.
if (isProfileOwner(uid)) {
return AccountManager.VISIBILITY_VISIBLE;
}
- // Apps with READ_CONTACTS permission get visibility by default even post O.
- boolean canReadContacts = checkReadContactsPermission(packageName, accounts.userId);
boolean preO = isPreOApplication(packageName);
if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)
- || (preO && checkGetAccountsPermission(packageName, accounts.userId))
- || canReadContacts || isPrivileged) {
+ || (preO && checkGetAccountsPermission(packageName, uid, accounts.userId))
+ || (checkReadContactsPermission(packageName, uid, accounts.userId)
+ && accountTypeManagesContacts(account.type, accounts.userId))
+ || isPrivileged) {
// Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
// match.
visibility = getAccountVisibilityFromCache(account,
@@ -5022,14 +5022,20 @@
}
}
- private boolean isPermittedForPackage(String packageName, int userId, String... permissions) {
+ private boolean isPermittedForPackage(String packageName, int uid, int userId,
+ String... permissions) {
final long identity = Binder.clearCallingIdentity();
try {
IPackageManager pm = ActivityThread.getPackageManager();
for (String perm : permissions) {
if (pm.checkPermission(perm, packageName, userId)
== PackageManager.PERMISSION_GRANTED) {
- return true;
+ // Checks runtime permission revocation.
+ final int opCode = AppOpsManager.permissionToOpCode(perm);
+ if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+ opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
}
}
} catch (RemoteException e) {
@@ -5145,13 +5151,37 @@
// Method checks visibility for applications targeing API level below {@link
// android.os.Build.VERSION_CODES#O},
// returns true if the the app has GET_ACCOUNTS or GET_ACCOUNTS_PRIVILEGED permission.
- private boolean checkGetAccountsPermission(String packageName, int userId) {
- return isPermittedForPackage(packageName, userId, Manifest.permission.GET_ACCOUNTS,
+ private boolean checkGetAccountsPermission(String packageName, int uid, int userId) {
+ return isPermittedForPackage(packageName, uid, userId, Manifest.permission.GET_ACCOUNTS,
Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
}
- private boolean checkReadContactsPermission(String packageName, int userId) {
- return isPermittedForPackage(packageName, userId, Manifest.permission.READ_CONTACTS);
+ private boolean checkReadContactsPermission(String packageName, int uid, int userId) {
+ return isPermittedForPackage(packageName, uid, userId, Manifest.permission.READ_CONTACTS);
+ }
+
+ // Heuristic to check that account type may be associated with some contacts data and
+ // therefore READ_CONTACTS permission grants the access to account by default.
+ private boolean accountTypeManagesContacts(String accountType, int userId) {
+ if (accountType == null) {
+ return false;
+ }
+ long identityToken = Binder.clearCallingIdentity();
+ Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
+ try {
+ serviceInfos = mAuthenticatorCache.getAllServices(userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ // Check contacts related permissions for authenticator.
+ for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo
+ : serviceInfos) {
+ if (accountType.equals(serviceInfo.type.type)) {
+ return isPermittedForPackage(serviceInfo.type.packageName, serviceInfo.uid, userId,
+ Manifest.permission.WRITE_CONTACTS);
+ }
+ }
+ return false;
}
/**