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.database.Cursor;
     27 import android.database.sqlite.SQLiteDatabase;
     28 import android.os.UserHandle;
     29 import android.text.TextUtils;
     30 import android.util.Log;
     31 import android.util.PackageUtils;
     32 import android.util.Pair;
     33 import android.util.Xml;
     34 import com.android.internal.annotations.GuardedBy;
     35 import com.android.internal.content.PackageMonitor;
     36 import com.android.internal.util.FastXmlSerializer;
     37 import com.android.internal.util.XmlUtils;
     38 import com.android.server.accounts.AccountsDb.DeDatabaseHelper;
     39 
     40 import org.xmlpull.v1.XmlPullParser;
     41 import org.xmlpull.v1.XmlPullParserException;
     42 import org.xmlpull.v1.XmlSerializer;
     43 
     44 import java.io.ByteArrayInputStream;
     45 import java.io.ByteArrayOutputStream;
     46 import java.io.IOException;
     47 import java.nio.charset.StandardCharsets;
     48 import java.util.ArrayList;
     49 import java.util.List;
     50 
     51 /**
     52  * Helper class for backup and restore of account access grants.
     53  */
     54 public final class AccountManagerBackupHelper {
     55     private static final String TAG = "AccountManagerBackupHelper";
     56 
     57     private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     58 
     59     private static final String TAG_PERMISSIONS = "permissions";
     60     private static final String TAG_PERMISSION = "permission";
     61     private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
     62     private static final String ATTR_PACKAGE = "package";
     63     private static final String ATTR_DIGEST = "digest";
     64 
     65     private final Object mLock = new Object();
     66 
     67     private final AccountManagerService mAccountManagerService;
     68     private final AccountManagerInternal mAccountManagerInternal;
     69 
     70     @GuardedBy("mLock")
     71     private List<PendingAppPermission> mRestorePendingAppPermissions;
     72 
     73     @GuardedBy("mLock")
     74     private RestorePackageMonitor mRestorePackageMonitor;
     75 
     76     @GuardedBy("mLock")
     77     private Runnable mRestoreCancelCommand;
     78 
     79     public AccountManagerBackupHelper(AccountManagerService accountManagerService,
     80             AccountManagerInternal accountManagerInternal) {
     81         mAccountManagerService = accountManagerService;
     82         mAccountManagerInternal = accountManagerInternal;
     83     }
     84 
     85     private final class PendingAppPermission {
     86         private final @NonNull String accountDigest;
     87         private final @NonNull String packageName;
     88         private final @NonNull String certDigest;
     89         private final @IntRange(from = 0) int userId;
     90 
     91         public PendingAppPermission(String accountDigest, String packageName,
     92                 String certDigest, int userId) {
     93             this.accountDigest = accountDigest;
     94             this.packageName = packageName;
     95             this.certDigest = certDigest;
     96             this.userId = userId;
     97         }
     98 
     99         public boolean apply(PackageManager packageManager) {
    100             Account account = null;
    101             AccountManagerService.UserAccounts accounts = mAccountManagerService
    102                     .getUserAccounts(userId);
    103             synchronized (accounts.dbLock) {
    104                 synchronized (accounts.cacheLock) {
    105                     for (Account[] accountsPerType : accounts.accountCache.values()) {
    106                         for (Account accountPerType : accountsPerType) {
    107                             if (accountDigest.equals(PackageUtils.computeSha256Digest(
    108                                     accountPerType.name.getBytes()))) {
    109                                 account = accountPerType;
    110                                 break;
    111                             }
    112                         }
    113                         if (account != null) {
    114                             break;
    115                         }
    116                     }
    117                 }
    118             }
    119             if (account == null) {
    120                 return false;
    121             }
    122             final PackageInfo packageInfo;
    123             try {
    124                 packageInfo = packageManager.getPackageInfoAsUser(packageName,
    125                         PackageManager.GET_SIGNATURES, userId);
    126             } catch (PackageManager.NameNotFoundException e) {
    127                 return false;
    128             }
    129             String currentCertDigest = PackageUtils.computeCertSha256Digest(
    130                     packageInfo.signatures[0]);
    131             if (!certDigest.equals(currentCertDigest)) {
    132                 return false;
    133             }
    134             final int uid = packageInfo.applicationInfo.uid;
    135             if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
    136                 mAccountManagerService.grantAppPermission(account,
    137                         AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
    138             }
    139             return true;
    140         }
    141     }
    142 
    143     public byte[] backupAccountAccessPermissions(int userId) {
    144         final AccountManagerService.UserAccounts accounts = mAccountManagerService
    145                 .getUserAccounts(userId);
    146         synchronized (accounts.dbLock) {
    147             synchronized (accounts.cacheLock) {
    148                 List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
    149                         .findAllAccountGrants();
    150                 if (allAccountGrants.isEmpty()) {
    151                     return null;
    152                 }
    153                 try {
    154                     ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
    155                     final XmlSerializer serializer = new FastXmlSerializer();
    156                     serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
    157                     serializer.startDocument(null, true);
    158                     serializer.startTag(null, TAG_PERMISSIONS);
    159 
    160                     PackageManager packageManager = mAccountManagerService.mContext
    161                             .getPackageManager();
    162                     for (Pair<String, Integer> grant : allAccountGrants) {
    163                         final String accountName = grant.first;
    164                         final int uid = grant.second;
    165 
    166                         final String[] packageNames = packageManager.getPackagesForUid(uid);
    167                         if (packageNames == null) {
    168                             continue;
    169                         }
    170 
    171                         for (String packageName : packageNames) {
    172                             String digest = PackageUtils.computePackageCertSha256Digest(
    173                                     packageManager, packageName, userId);
    174                             if (digest != null) {
    175                                 serializer.startTag(null, TAG_PERMISSION);
    176                                 serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
    177                                         PackageUtils.computeSha256Digest(accountName.getBytes()));
    178                                 serializer.attribute(null, ATTR_PACKAGE, packageName);
    179                                 serializer.attribute(null, ATTR_DIGEST, digest);
    180                                 serializer.endTag(null, TAG_PERMISSION);
    181                             }
    182                         }
    183                     }
    184                     serializer.endTag(null, TAG_PERMISSIONS);
    185                     serializer.endDocument();
    186                     serializer.flush();
    187                     return dataStream.toByteArray();
    188                 } catch (IOException e) {
    189                     Log.e(TAG, "Error backing up account access grants", e);
    190                     return null;
    191                 }
    192             }
    193         }
    194     }
    195 
    196     public void restoreAccountAccessPermissions(byte[] data, int userId) {
    197         try {
    198             ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
    199             XmlPullParser parser = Xml.newPullParser();
    200             parser.setInput(dataStream, StandardCharsets.UTF_8.name());
    201             PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
    202 
    203             final int permissionsOuterDepth = parser.getDepth();
    204             while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
    205                 if (!TAG_PERMISSIONS.equals(parser.getName())) {
    206                     continue;
    207                 }
    208                 final int permissionOuterDepth = parser.getDepth();
    209                 while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
    210                     if (!TAG_PERMISSION.equals(parser.getName())) {
    211                         continue;
    212                     }
    213                     String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
    214                     if (TextUtils.isEmpty(accountDigest)) {
    215                         XmlUtils.skipCurrentTag(parser);
    216                     }
    217                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
    218                     if (TextUtils.isEmpty(packageName)) {
    219                         XmlUtils.skipCurrentTag(parser);
    220                     }
    221                     String digest =  parser.getAttributeValue(null, ATTR_DIGEST);
    222                     if (TextUtils.isEmpty(digest)) {
    223                         XmlUtils.skipCurrentTag(parser);
    224                     }
    225 
    226                     PendingAppPermission pendingAppPermission = new PendingAppPermission(
    227                             accountDigest, packageName, digest, userId);
    228 
    229                     if (!pendingAppPermission.apply(packageManager)) {
    230                         synchronized (mLock) {
    231                             // Start watching before add pending to avoid a missed signal
    232                             if (mRestorePackageMonitor == null) {
    233                                 mRestorePackageMonitor = new RestorePackageMonitor();
    234                                 mRestorePackageMonitor.register(mAccountManagerService.mContext,
    235                                         mAccountManagerService.mHandler.getLooper(), true);
    236                             }
    237                             if (mRestorePendingAppPermissions == null) {
    238                                 mRestorePendingAppPermissions = new ArrayList<>();
    239                             }
    240                             mRestorePendingAppPermissions.add(pendingAppPermission);
    241                         }
    242                     }
    243                 }
    244             }
    245 
    246             // Make sure we eventually prune the in-memory pending restores
    247             mRestoreCancelCommand = new CancelRestoreCommand();
    248             mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
    249                     PENDING_RESTORE_TIMEOUT_MILLIS);
    250         } catch (XmlPullParserException | IOException e) {
    251             Log.e(TAG, "Error restoring app permissions", e);
    252         }
    253     }
    254 
    255     private final class RestorePackageMonitor extends PackageMonitor {
    256         @Override
    257         public void onPackageAdded(String packageName, int uid) {
    258             synchronized (mLock) {
    259                 if (mRestorePendingAppPermissions == null) {
    260                     return;
    261                 }
    262                 if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
    263                     return;
    264                 }
    265                 final int count = mRestorePendingAppPermissions.size();
    266                 for (int i = count - 1; i >= 0; i--) {
    267                     PendingAppPermission pendingAppPermission =
    268                             mRestorePendingAppPermissions.get(i);
    269                     if (!pendingAppPermission.packageName.equals(packageName)) {
    270                         continue;
    271                     }
    272                     if (pendingAppPermission.apply(
    273                             mAccountManagerService.mContext.getPackageManager())) {
    274                         mRestorePendingAppPermissions.remove(i);
    275                     }
    276                 }
    277                 if (mRestorePendingAppPermissions.isEmpty()
    278                         && mRestoreCancelCommand != null) {
    279                     mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
    280                     mRestoreCancelCommand.run();
    281                     mRestoreCancelCommand = null;
    282                 }
    283             }
    284         }
    285     }
    286 
    287     private final class CancelRestoreCommand implements Runnable {
    288         @Override
    289         public void run() {
    290             synchronized (mLock) {
    291                 mRestorePendingAppPermissions = null;
    292                 if (mRestorePackageMonitor != null) {
    293                     mRestorePackageMonitor.unregister();
    294                     mRestorePackageMonitor = null;
    295                 }
    296             }
    297         }
    298     }
    299 }
    300