/*
|
* 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.content.pm.Signature;
|
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.Pair;
|
import android.util.Slog;
|
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 com.android.server.accounts.AccountsDb.DeDatabaseHelper;
|
|
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 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.dbLock) {
|
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;
|
}
|
|
// Before we used only the first signature to compute the SHA 256 but some
|
// apps could be singed by multiple certs and the cert order is undefined.
|
// We prefer the modern computation procedure where all certs are taken
|
// into account but also allow the value from the old computation to allow
|
// restoring backed up grants on an older platform version.
|
final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
|
packageInfo.signatures);
|
final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
|
signaturesSha256Digests);
|
if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
|
|| !certDigest.equals(signaturesSha256Digests[0]))) {
|
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.dbLock) {
|
synchronized (accounts.cacheLock) {
|
List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
|
.findAllAccountGrants();
|
if (allAccountGrants.isEmpty()) {
|
return null;
|
}
|
try {
|
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
|
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();
|
for (Pair<String, Integer> grant : allAccountGrants) {
|
final String accountName = grant.first;
|
final int uid = grant.second;
|
|
final String[] packageNames = packageManager.getPackagesForUid(uid);
|
if (packageNames == null) {
|
continue;
|
}
|
|
for (String packageName : packageNames) {
|
final PackageInfo packageInfo;
|
try {
|
packageInfo = packageManager.getPackageInfoAsUser(packageName,
|
PackageManager.GET_SIGNATURES, userId);
|
} catch (PackageManager.NameNotFoundException e) {
|
Slog.i(TAG, "Skipping backup of account access grant for"
|
+ " non-existing package: " + packageName);
|
continue;
|
}
|
final String digest = PackageUtils.computeSignaturesSha256Digest(
|
packageInfo.signatures);
|
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);
|
}
|
}
|
}
|
serializer.endTag(null, TAG_PERMISSIONS);
|
serializer.endDocument();
|
serializer.flush();
|
return dataStream.toByteArray();
|
} catch (IOException e) {
|
Log.e(TAG, "Error backing up account access grants", e);
|
return null;
|
}
|
}
|
}
|
}
|
|
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.mHandler.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.mHandler.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.mHandler.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;
|
}
|
}
|
}
|
}
|
}
|