AccountManagerBackupHelper.java revision 5d09c998a03eea53218c3b3c40e20db1b7693c9c
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
17package com.android.server.accounts;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.AccountManagerInternal;
22import android.annotation.IntRange;
23import android.annotation.NonNull;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.database.Cursor;
27import android.database.sqlite.SQLiteDatabase;
28import android.os.UserHandle;
29import android.text.TextUtils;
30import android.util.Log;
31import android.util.PackageUtils;
32import android.util.Xml;
33import com.android.internal.annotations.GuardedBy;
34import com.android.internal.content.PackageMonitor;
35import com.android.internal.util.FastXmlSerializer;
36import com.android.internal.util.XmlUtils;
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39import org.xmlpull.v1.XmlSerializer;
40
41import java.io.ByteArrayInputStream;
42import java.io.ByteArrayOutputStream;
43import java.io.IOException;
44import java.nio.charset.StandardCharsets;
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * Helper class for backup and restore of account access grants.
50 */
51public 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.mHandler.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            mRestoreCancelCommand = new CancelRestoreCommand();
261            mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
262                    PENDING_RESTORE_TIMEOUT_MILLIS);
263        } catch (XmlPullParserException | IOException e) {
264            Log.e(TAG, "Error restoring app permissions", e);
265        }
266    }
267
268    private final class RestorePackageMonitor extends PackageMonitor {
269        @Override
270        public void onPackageAdded(String packageName, int uid) {
271            synchronized (mLock) {
272                if (mRestorePendingAppPermissions == null) {
273                    return;
274                }
275                if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
276                    return;
277                }
278                final int count = mRestorePendingAppPermissions.size();
279                for (int i = count - 1; i >= 0; i--) {
280                    PendingAppPermission pendingAppPermission =
281                            mRestorePendingAppPermissions.get(i);
282                    if (!pendingAppPermission.packageName.equals(packageName)) {
283                        continue;
284                    }
285                    if (pendingAppPermission.apply(
286                            mAccountManagerService.mContext.getPackageManager())) {
287                        mRestorePendingAppPermissions.remove(i);
288                    }
289                }
290                if (mRestorePendingAppPermissions.isEmpty()
291                        && mRestoreCancelCommand != null) {
292                    mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
293                    mRestoreCancelCommand.run();
294                    mRestoreCancelCommand = null;
295                }
296            }
297        }
298    }
299
300    private final class CancelRestoreCommand implements Runnable {
301        @Override
302        public void run() {
303            synchronized (mLock) {
304                mRestorePendingAppPermissions = null;
305                if (mRestorePackageMonitor != null) {
306                    mRestorePackageMonitor.unregister();
307                    mRestorePackageMonitor = null;
308                }
309            }
310        }
311    }
312}
313