Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.accounts;
     18 
     19 import android.accounts.Account;
     20 import android.accounts.AccountManager;
     21 import android.accounts.AccountManagerInternal;
     22 import android.annotation.IntRange;
     23 import android.annotation.NonNull;
     24 import android.content.pm.PackageInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.Signature;
     27 import android.database.Cursor;
     28 import android.database.sqlite.SQLiteDatabase;
     29 import android.os.UserHandle;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 import android.util.PackageUtils;
     33 import android.util.Pair;
     34 import android.util.Slog;
     35 import android.util.Xml;
     36 import com.android.internal.annotations.GuardedBy;
     37 import com.android.internal.content.PackageMonitor;
     38 import com.android.internal.util.FastXmlSerializer;
     39 import com.android.internal.util.XmlUtils;
     40 import com.android.server.accounts.AccountsDb.DeDatabaseHelper;
     41 
     42 import org.xmlpull.v1.XmlPullParser;
     43 import org.xmlpull.v1.XmlPullParserException;
     44 import org.xmlpull.v1.XmlSerializer;
     45 
     46 import java.io.ByteArrayInputStream;
     47 import java.io.ByteArrayOutputStream;
     48 import java.io.IOException;
     49 import java.nio.charset.StandardCharsets;
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 
     53 /**
     54  * Helper class for backup and restore of account access grants.
     55  */
     56 public final class AccountManagerBackupHelper {
     57     private static final String TAG = "AccountManagerBackupHelper";
     58 
     59     private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     60 
     61     private static final String TAG_PERMISSIONS = "permissions";
     62     private static final String TAG_PERMISSION = "permission";
     63     private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
     64     private static final String ATTR_PACKAGE = "package";
     65     private static final String ATTR_DIGEST = "digest";
     66 
     67     private final Object mLock = new Object();
     68 
     69     private final AccountManagerService mAccountManagerService;
     70     private final AccountManagerInternal mAccountManagerInternal;
     71 
     72     @GuardedBy("mLock")
     73     private List<PendingAppPermission> mRestorePendingAppPermissions;
     74 
     75     @GuardedBy("mLock")
     76     private RestorePackageMonitor mRestorePackageMonitor;
     77 
     78     @GuardedBy("mLock")
     79     private Runnable mRestoreCancelCommand;
     80 
     81     public AccountManagerBackupHelper(AccountManagerService accountManagerService,
     82             AccountManagerInternal accountManagerInternal) {
     83         mAccountManagerService = accountManagerService;
     84         mAccountManagerInternal = accountManagerInternal;
     85     }
     86 
     87     private final class PendingAppPermission {
     88         private final @NonNull String accountDigest;
     89         private final @NonNull String packageName;
     90         private final @NonNull String certDigest;
     91         private final @IntRange(from = 0) int userId;
     92 
     93         public PendingAppPermission(String accountDigest, String packageName,
     94                 String certDigest, int userId) {
     95             this.accountDigest = accountDigest;
     96             this.packageName = packageName;
     97             this.certDigest = certDigest;
     98             this.userId = userId;
     99         }
    100 
    101         public boolean apply(PackageManager packageManager) {
    102             Account account = null;
    103             AccountManagerService.UserAccounts accounts = mAccountManagerService
    104                     .getUserAccounts(userId);
    105             synchronized (accounts.dbLock) {
    106                 synchronized (accounts.cacheLock) {
    107                     for (Account[] accountsPerType : accounts.accountCache.values()) {
    108                         for (Account accountPerType : accountsPerType) {
    109                             if (accountDigest.equals(PackageUtils.computeSha256Digest(
    110                                     accountPerType.name.getBytes()))) {
    111                                 account = accountPerType;
    112                                 break;
    113                             }
    114                         }
    115                         if (account != null) {
    116                             break;
    117                         }
    118                     }
    119                 }
    120             }
    121             if (account == null) {
    122                 return false;
    123             }
    124             final PackageInfo packageInfo;
    125             try {
    126                 packageInfo = packageManager.getPackageInfoAsUser(packageName,
    127                         PackageManager.GET_SIGNATURES, userId);
    128             } catch (PackageManager.NameNotFoundException e) {
    129                 return false;
    130             }
    131 
    132             // Before we used only the first signature to compute the SHA 256 but some
    133             // apps could be singed by multiple certs and the cert order is undefined.
    134             // We prefer the modern computation procedure where all certs are taken
    135             // into account but also allow the value from the old computation to allow
    136             // restoring backed up grants on an older platform version.
    137             final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
    138                     packageInfo.signatures);
    139             final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
    140                     signaturesSha256Digests);
    141             if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
    142                     || !certDigest.equals(signaturesSha256Digests[0]))) {
    143                 return false;
    144             }
    145             final int uid = packageInfo.applicationInfo.uid;
    146             if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
    147                 mAccountManagerService.grantAppPermission(account,
    148                         AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
    149             }
    150             return true;
    151         }
    152     }
    153 
    154     public byte[] backupAccountAccessPermissions(int userId) {
    155         final AccountManagerService.UserAccounts accounts = mAccountManagerService
    156                 .getUserAccounts(userId);
    157         synchronized (accounts.dbLock) {
    158             synchronized (accounts.cacheLock) {
    159                 List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
    160                         .findAllAccountGrants();
    161                 if (allAccountGrants.isEmpty()) {
    162                     return null;
    163                 }
    164                 try {
    165                     ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
    166                     final XmlSerializer serializer = new FastXmlSerializer();
    167                     serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
    168                     serializer.startDocument(null, true);
    169                     serializer.startTag(null, TAG_PERMISSIONS);
    170 
    171                     PackageManager packageManager = mAccountManagerService.mContext
    172                             .getPackageManager();
    173                     for (Pair<String, Integer> grant : allAccountGrants) {
    174                         final String accountName = grant.first;
    175                         final int uid = grant.second;
    176 
    177                         final String[] packageNames = packageManager.getPackagesForUid(uid);
    178                         if (packageNames == null) {
    179                             continue;
    180                         }
    181 
    182                         for (String packageName : packageNames) {
    183                             final PackageInfo packageInfo;
    184                             try {
    185                                 packageInfo = packageManager.getPackageInfoAsUser(packageName,
    186                                         PackageManager.GET_SIGNATURES, userId);
    187                             } catch (PackageManager.NameNotFoundException e) {
    188                                 Slog.i(TAG, "Skipping backup of account access grant for"
    189                                         + " non-existing package: " + packageName);
    190                                 continue;
    191                             }
    192                             final String digest = PackageUtils.computeSignaturesSha256Digest(
    193                                     packageInfo.signatures);
    194                             if (digest != null) {
    195                                 serializer.startTag(null, TAG_PERMISSION);
    196                                 serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
    197                                         PackageUtils.computeSha256Digest(accountName.getBytes()));
    198                                 serializer.attribute(null, ATTR_PACKAGE, packageName);
    199                                 serializer.attribute(null, ATTR_DIGEST, digest);
    200                                 serializer.endTag(null, TAG_PERMISSION);
    201                             }
    202                         }
    203                     }
    204                     serializer.endTag(null, TAG_PERMISSIONS);
    205                     serializer.endDocument();
    206                     serializer.flush();
    207                     return dataStream.toByteArray();
    208                 } catch (IOException e) {
    209                     Log.e(TAG, "Error backing up account access grants", e);
    210                     return null;
    211                 }
    212             }
    213         }
    214     }
    215 
    216     public void restoreAccountAccessPermissions(byte[] data, int userId) {
    217         try {
    218             ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
    219             XmlPullParser parser = Xml.newPullParser();
    220             parser.setInput(dataStream, StandardCharsets.UTF_8.name());
    221             PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
    222 
    223             final int permissionsOuterDepth = parser.getDepth();
    224             while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
    225                 if (!TAG_PERMISSIONS.equals(parser.getName())) {
    226                     continue;
    227                 }
    228                 final int permissionOuterDepth = parser.getDepth();
    229                 while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
    230                     if (!TAG_PERMISSION.equals(parser.getName())) {
    231                         continue;
    232                     }
    233                     String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
    234                     if (TextUtils.isEmpty(accountDigest)) {
    235                         XmlUtils.skipCurrentTag(parser);
    236                     }
    237                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
    238                     if (TextUtils.isEmpty(packageName)) {
    239                         XmlUtils.skipCurrentTag(parser);
    240                     }
    241                     String digest =  parser.getAttributeValue(null, ATTR_DIGEST);
    242                     if (TextUtils.isEmpty(digest)) {
    243                         XmlUtils.skipCurrentTag(parser);
    244                     }
    245 
    246                     PendingAppPermission pendingAppPermission = new PendingAppPermission(
    247                             accountDigest, packageName, digest, userId);
    248 
    249                     if (!pendingAppPermission.apply(packageManager)) {
    250                         synchronized (mLock) {
    251                             // Start watching before add pending to avoid a missed signal
    252                             if (mRestorePackageMonitor == null) {
    253                                 mRestorePackageMonitor = new RestorePackageMonitor();
    254                                 mRestorePackageMonitor.register(mAccountManagerService.mContext,
    255                                         mAccountManagerService.mHandler.getLooper(), true);
    256                             }
    257                             if (mRestorePendingAppPermissions == null) {
    258                                 mRestorePendingAppPermissions = new ArrayList<>();
    259                             }
    260                             mRestorePendingAppPermissions.add(pendingAppPermission);
    261                         }
    262                     }
    263                 }
    264             }
    265 
    266             // Make sure we eventually prune the in-memory pending restores
    267             mRestoreCancelCommand = new CancelRestoreCommand();
    268             mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
    269                     PENDING_RESTORE_TIMEOUT_MILLIS);
    270         } catch (XmlPullParserException | IOException e) {
    271             Log.e(TAG, "Error restoring app permissions", e);
    272         }
    273     }
    274 
    275     private final class RestorePackageMonitor extends PackageMonitor {
    276         @Override
    277         public void onPackageAdded(String packageName, int uid) {
    278             synchronized (mLock) {
    279                 if (mRestorePendingAppPermissions == null) {
    280                     return;
    281                 }
    282                 if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
    283                     return;
    284                 }
    285                 final int count = mRestorePendingAppPermissions.size();
    286                 for (int i = count - 1; i >= 0; i--) {
    287                     PendingAppPermission pendingAppPermission =
    288                             mRestorePendingAppPermissions.get(i);
    289                     if (!pendingAppPermission.packageName.equals(packageName)) {
    290                         continue;
    291                     }
    292                     if (pendingAppPermission.apply(
    293                             mAccountManagerService.mContext.getPackageManager())) {
    294                         mRestorePendingAppPermissions.remove(i);
    295                     }
    296                 }
    297                 if (mRestorePendingAppPermissions.isEmpty()
    298                         && mRestoreCancelCommand != null) {
    299                     mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
    300                     mRestoreCancelCommand.run();
    301                     mRestoreCancelCommand = null;
    302                 }
    303             }
    304         }
    305     }
    306 
    307     private final class CancelRestoreCommand implements Runnable {
    308         @Override
    309         public void run() {
    310             synchronized (mLock) {
    311                 mRestorePendingAppPermissions = null;
    312                 if (mRestorePackageMonitor != null) {
    313                     mRestorePackageMonitor.unregister();
    314                     mRestorePackageMonitor = null;
    315                 }
    316             }
    317         }
    318     }
    319 }
    320