[DO NOT MERGE] Backup account access grants

am: 72ed12c55f

Change-Id: I80029a2db1cbddc22ae545879fd757446610e154
This commit is contained in:
Svet Ganov
2016-09-10 00:20:52 +00:00
committed by android-build-merger
6 changed files with 552 additions and 23 deletions

View File

@@ -73,4 +73,18 @@ public abstract class AccountManagerInternal {
*/
public abstract void addOnAppPermissionChangeListener(
@NonNull OnAppPermissionChangeListener listener);
/**
* Backups the account access permissions.
* @param userId The user for which to backup.
* @return The backup data.
*/
public abstract byte[] backupAccountAccessPermissions(int userId);
/**
* Restores the account access permissions.
* @param data The restore data.
* @param userId The user for which to restore.
*/
public abstract void restoreAccountAccessPermissions(byte[] data, int userId);
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2016 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 android.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Helper functions applicable to packages.
* @hide
*/
public final class PackageUtils {
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private PackageUtils() {
/* hide constructor */
}
/**
* Computes the SHA256 digest of the signing cert for a package.
* @param packageManager The package manager.
* @param packageName The package for which to generate the digest.
* @param userId The user for which to generate the digest.
* @return The digest or null if the package does not exist for this user.
*/
public static @Nullable String computePackageCertSha256Digest(
@NonNull PackageManager packageManager,
@NonNull String packageName, int userId) {
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
return computeCertSha256Digest(packageInfo.signatures[0]);
}
/**
* Computes the SHA256 digest of a cert.
* @param signature The signature.
* @return The digest or null if an error occurs.
*/
public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) {
return computeSha256Digest(signature.toByteArray());
}
/**
* Computes the SHA256 digest of some data.
* @param data The data.
* @return The digest or null if an error occurs.
*/
public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
/* can't happen */
return null;
}
messageDigest.update(data);
final byte[] digest = messageDigest.digest();
final int digestLength = digest.length;
final int charCount = 2 * digestLength;
final char[] chars = new char[charCount];
for (int i = 0; i < digestLength; i++) {
final int byteHex = digest[i] & 0xFF;
chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
}
return new String(chars);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2016 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.backup;
import android.accounts.AccountManagerInternal;
import android.app.backup.BlobBackupHelper;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.LocalServices;
/**
* Helper for handling backup of account manager specific state.
*/
public class AccountManagerBackupHelper extends BlobBackupHelper {
private static final String TAG = "AccountsBackup";
private static final boolean DEBUG = false;
// current schema of the backup state blob
private static final int STATE_VERSION = 1;
// key under which the account access grant state blob is committed to backup
private static final String KEY_ACCOUNT_ACCESS_GRANTS = "account_access_grants";
public AccountManagerBackupHelper() {
super(STATE_VERSION, KEY_ACCOUNT_ACCESS_GRANTS);
}
@Override
protected byte[] getBackupPayload(String key) {
AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class);
if (DEBUG) {
Slog.d(TAG, "Handling backup of " + key);
}
try {
switch (key) {
case KEY_ACCOUNT_ACCESS_GRANTS: {
return am.backupAccountAccessPermissions(UserHandle.USER_SYSTEM);
}
default: {
Slog.w(TAG, "Unexpected backup key " + key);
}
}
} catch (Exception e) {
Slog.e(TAG, "Unable to store payload " + key);
}
return new byte[0];
}
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class);
if (DEBUG) {
Slog.d(TAG, "Handling restore of " + key);
}
try {
switch (key) {
case KEY_ACCOUNT_ACCESS_GRANTS: {
am.restoreAccountAccessPermissions(payload, UserHandle.USER_SYSTEM);
} break;
default: {
Slog.w(TAG, "Unexpected restore key " + key);
}
}
} catch (Exception e) {
Slog.w(TAG, "Unable to restore key " + key);
}
}
}

View File

@@ -49,6 +49,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String PERMISSION_HELPER = "permissions";
private static final String USAGE_STATS_HELPER = "usage_stats";
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -82,6 +83,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -111,6 +113,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);

View File

@@ -0,0 +1,312 @@
/*
* Copyright (C) 2016 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.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for backup and restore of account access grants.
*/
public final class AccountManagerBackupHelper {
private static final String TAG = "AccountManagerBackupHelper";
private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final String TAG_PERMISSIONS = "permissions";
private static final String TAG_PERMISSION = "permission";
private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_DIGEST = "digest";
private static final String ACCOUNT_ACCESS_GRANTS = ""
+ "SELECT " + AccountManagerService.ACCOUNTS_NAME + ", "
+ AccountManagerService.GRANTS_GRANTEE_UID
+ " FROM " + AccountManagerService.TABLE_ACCOUNTS
+ ", " + AccountManagerService.TABLE_GRANTS
+ " WHERE " + AccountManagerService.GRANTS_ACCOUNTS_ID
+ "=" + AccountManagerService.ACCOUNTS_ID;
private final Object mLock = new Object();
private final AccountManagerService mAccountManagerService;
private final AccountManagerInternal mAccountManagerInternal;
@GuardedBy("mLock")
private List<PendingAppPermission> mRestorePendingAppPermissions;
@GuardedBy("mLock")
private RestorePackageMonitor mRestorePackageMonitor;
@GuardedBy("mLock")
private Runnable mRestoreCancelCommand;
public AccountManagerBackupHelper(AccountManagerService accountManagerService,
AccountManagerInternal accountManagerInternal) {
mAccountManagerService = accountManagerService;
mAccountManagerInternal = accountManagerInternal;
}
private final class PendingAppPermission {
private final @NonNull String accountDigest;
private final @NonNull String packageName;
private final @NonNull String certDigest;
private final @IntRange(from = 0) int userId;
public PendingAppPermission(String accountDigest, String packageName,
String certDigest, int userId) {
this.accountDigest = accountDigest;
this.packageName = packageName;
this.certDigest = certDigest;
this.userId = userId;
}
public boolean apply(PackageManager packageManager) {
Account account = null;
AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
synchronized (accounts.cacheLock) {
for (Account[] accountsPerType : accounts.accountCache.values()) {
for (Account accountPerType : accountsPerType) {
if (accountDigest.equals(PackageUtils.computeSha256Digest(
accountPerType.name.getBytes()))) {
account = accountPerType;
break;
}
}
if (account != null) {
break;
}
}
}
if (account == null) {
return false;
}
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
String currentCertDigest = PackageUtils.computeCertSha256Digest(
packageInfo.signatures[0]);
if (!certDigest.equals(currentCertDigest)) {
return false;
}
final int uid = packageInfo.applicationInfo.uid;
if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
mAccountManagerService.grantAppPermission(account,
AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
}
return true;
}
}
public byte[] backupAccountAccessPermissions(int userId) {
final AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
synchronized (accounts.cacheLock) {
SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
try (
Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null);
) {
if (cursor == null || !cursor.moveToFirst()) {
return null;
}
final int nameColumnIdx = cursor.getColumnIndex(
AccountManagerService.ACCOUNTS_NAME);
final int uidColumnIdx = cursor.getColumnIndex(
AccountManagerService.GRANTS_GRANTEE_UID);
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
try {
final XmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.startTag(null, TAG_PERMISSIONS);
PackageManager packageManager = mAccountManagerService.mContext
.getPackageManager();
do {
final String accountName = cursor.getString(nameColumnIdx);
final int uid = cursor.getInt(uidColumnIdx);
final String[] packageNames = packageManager.getPackagesForUid(uid);
if (packageNames == null) {
continue;
}
for (String packageName : packageNames) {
String digest = PackageUtils.computePackageCertSha256Digest(
packageManager, packageName, userId);
if (digest != null) {
serializer.startTag(null, TAG_PERMISSION);
serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
PackageUtils.computeSha256Digest(accountName.getBytes()));
serializer.attribute(null, ATTR_PACKAGE, packageName);
serializer.attribute(null, ATTR_DIGEST, digest);
serializer.endTag(null, TAG_PERMISSION);
}
}
} while (cursor.moveToNext());
serializer.endTag(null, TAG_PERMISSIONS);
serializer.endDocument();
serializer.flush();
} catch (IOException e) {
Log.e(TAG, "Error backing up account access grants", e);
return null;
}
return dataStream.toByteArray();
}
}
}
public void restoreAccountAccessPermissions(byte[] data, int userId) {
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(dataStream, StandardCharsets.UTF_8.name());
PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
final int permissionsOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
if (!TAG_PERMISSIONS.equals(parser.getName())) {
continue;
}
final int permissionOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
if (!TAG_PERMISSION.equals(parser.getName())) {
continue;
}
String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
if (TextUtils.isEmpty(accountDigest)) {
XmlUtils.skipCurrentTag(parser);
}
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
if (TextUtils.isEmpty(packageName)) {
XmlUtils.skipCurrentTag(parser);
}
String digest = parser.getAttributeValue(null, ATTR_DIGEST);
if (TextUtils.isEmpty(digest)) {
XmlUtils.skipCurrentTag(parser);
}
PendingAppPermission pendingAppPermission = new PendingAppPermission(
accountDigest, packageName, digest, userId);
if (!pendingAppPermission.apply(packageManager)) {
synchronized (mLock) {
// Start watching before add pending to avoid a missed signal
if (mRestorePackageMonitor == null) {
mRestorePackageMonitor = new RestorePackageMonitor();
mRestorePackageMonitor.register(mAccountManagerService.mContext,
mAccountManagerService.mMessageHandler.getLooper(), true);
}
if (mRestorePendingAppPermissions == null) {
mRestorePendingAppPermissions = new ArrayList<>();
}
mRestorePendingAppPermissions.add(pendingAppPermission);
}
}
}
}
// Make sure we eventually prune the in-memory pending restores
mRestoreCancelCommand = new CancelRestoreCommand();
mAccountManagerService.mMessageHandler.postDelayed(mRestoreCancelCommand,
PENDING_RESTORE_TIMEOUT_MILLIS);
} catch (XmlPullParserException | IOException e) {
Log.e(TAG, "Error restoring app permissions", e);
}
}
private final class RestorePackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
synchronized (mLock) {
if (mRestorePendingAppPermissions == null) {
return;
}
if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
return;
}
final int count = mRestorePendingAppPermissions.size();
for (int i = count - 1; i >= 0; i--) {
PendingAppPermission pendingAppPermission =
mRestorePendingAppPermissions.get(i);
if (!pendingAppPermission.packageName.equals(packageName)) {
continue;
}
if (pendingAppPermission.apply(
mAccountManagerService.mContext.getPackageManager())) {
mRestorePendingAppPermissions.remove(i);
}
}
if (mRestorePendingAppPermissions.isEmpty()
&& mRestoreCancelCommand != null) {
mAccountManagerService.mMessageHandler.removeCallbacks(mRestoreCancelCommand);
mRestoreCancelCommand.run();
mRestoreCancelCommand = null;
}
}
}
}
private final class CancelRestoreCommand implements Runnable {
@Override
public void run() {
synchronized (mLock) {
mRestorePendingAppPermissions = null;
if (mRestorePackageMonitor != null) {
mRestorePackageMonitor.unregister();
mRestorePackageMonitor = null;
}
}
}
}
}

View File

@@ -89,12 +89,14 @@ import android.os.UserManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
@@ -156,13 +158,6 @@ public class AccountManagerService
publishBinderService(Context.ACCOUNT_SERVICE, mService);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mService.systemReady();
}
}
@Override
public void onUnlockUser(int userHandle) {
mService.onUnlockUser(userHandle);
@@ -176,13 +171,13 @@ public class AccountManagerService
private static final int MAX_DEBUG_DB_SIZE = 64;
private final Context mContext;
final Context mContext;
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private UserManager mUserManager;
private final MessageHandler mMessageHandler;
final MessageHandler mMessageHandler;
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
@@ -190,9 +185,9 @@ public class AccountManagerService
private final IAccountAuthenticatorCache mAuthenticatorCache;
private static final String TABLE_ACCOUNTS = "accounts";
private static final String ACCOUNTS_ID = "_id";
private static final String ACCOUNTS_NAME = "name";
static final String TABLE_ACCOUNTS = "accounts";
static final String ACCOUNTS_ID = "_id";
static final String ACCOUNTS_NAME = "name";
private static final String ACCOUNTS_TYPE = "type";
private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
private static final String ACCOUNTS_PASSWORD = "password";
@@ -206,10 +201,10 @@ public class AccountManagerService
private static final String AUTHTOKENS_TYPE = "type";
private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
private static final String TABLE_GRANTS = "grants";
private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
static final String TABLE_GRANTS = "grants";
static final String GRANTS_ACCOUNTS_ID = "accounts_id";
private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
private static final String GRANTS_GRANTEE_UID = "uid";
static final String GRANTS_GRANTEE_UID = "uid";
private static final String TABLE_EXTRAS = "extras";
private static final String EXTRAS_ID = "_id";
@@ -276,15 +271,15 @@ public class AccountManagerService
static class UserAccounts {
private final int userId;
private final DeDatabaseHelper openHelper;
final DeDatabaseHelper openHelper;
private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
credentialsPermissionNotificationIds =
new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
private final HashMap<Account, Integer> signinRequiredNotificationIds =
new HashMap<Account, Integer>();
private final Object cacheLock = new Object();
final Object cacheLock = new Object();
/** protected by the {@link #cacheLock} */
private final HashMap<String, Account[]> accountCache =
final HashMap<String, Account[]> accountCache =
new LinkedHashMap<>();
/** protected by the {@link #cacheLock} */
private final HashMap<Account, HashMap<String, String>> userDataCache =
@@ -526,9 +521,6 @@ public class AccountManagerService
}
}
public void systemReady() {
}
private UserManager getUserManager() {
if (mUserManager == null) {
mUserManager = UserManager.get(mContext);
@@ -4453,7 +4445,7 @@ public class AccountManagerService
}
}
private class MessageHandler extends Handler {
class MessageHandler extends Handler {
MessageHandler(Looper looper) {
super(looper);
}
@@ -5604,7 +5596,7 @@ public class AccountManagerService
* which is in the system. This means we don't need to protect it with permissions.
* @hide
*/
private void grantAppPermission(Account account, String authTokenType, int uid) {
void grantAppPermission(Account account, String authTokenType, int uid) {
if (account == null || authTokenType == null) {
Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
return;
@@ -5989,6 +5981,11 @@ public class AccountManagerService
}
private final class AccountManagerInternalImpl extends AccountManagerInternal {
private final Object mLock = new Object();
@GuardedBy("mLock")
private AccountManagerBackupHelper mBackupHelper;
@Override
public void requestAccountAccess(@NonNull Account account, @NonNull String packageName,
@IntRange(from = 0) int userId, @NonNull RemoteCallback callback) {
@@ -6043,5 +6040,27 @@ public class AccountManagerService
public boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid) {
return AccountManagerService.this.hasAccountAccess(account, null, uid);
}
@Override
public byte[] backupAccountAccessPermissions(int userId) {
synchronized (mLock) {
if (mBackupHelper == null) {
mBackupHelper = new AccountManagerBackupHelper(
AccountManagerService.this, this);
}
return mBackupHelper.backupAccountAccessPermissions(userId);
}
}
@Override
public void restoreAccountAccessPermissions(byte[] data, int userId) {
synchronized (mLock) {
if (mBackupHelper == null) {
mBackupHelper = new AccountManagerBackupHelper(
AccountManagerService.this, this);
}
mBackupHelper.restoreAccountAccessPermissions(data, userId);
}
}
}
}