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