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