Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d42e5e7 authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Backup account access grants"

parents 96e602e1 5d09c998
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -73,4 +73,18 @@ public abstract class AccountManagerInternal {
     */
    public abstract void addOnAppPermissionChangeListener(
            @NonNull OnAppPermissionChangeListener listener);

    /**
     * Backups the account access permissions.
     * @param userId The user for which to backup.
     * @return The backup data.
     */
    public abstract byte[] backupAccountAccessPermissions(int userId);

    /**
     * Restores the account access permissions.
     * @param data The restore data.
     * @param userId The user for which to restore.
     */
    public abstract void restoreAccountAccessPermissions(byte[] data, int userId);
}
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Helper functions applicable to packages.
 * @hide
 */
public final class PackageUtils {
    private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    private PackageUtils() {
        /* hide constructor */
    }

    /**
     * Computes the SHA256 digest of the signing cert for a package.
     * @param packageManager The package manager.
     * @param packageName The package for which to generate the digest.
     * @param userId The user for which to generate the digest.
     * @return The digest or null if the package does not exist for this user.
     */
    public static @Nullable String computePackageCertSha256Digest(
            @NonNull PackageManager packageManager,
            @NonNull String packageName, int userId) {
        final PackageInfo packageInfo;
        try {
            packageInfo = packageManager.getPackageInfoAsUser(packageName,
                    PackageManager.GET_SIGNATURES, userId);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
        return computeCertSha256Digest(packageInfo.signatures[0]);
    }

    /**
     * Computes the SHA256 digest of a cert.
     * @param signature The signature.
     * @return The digest or null if an error occurs.
     */
    public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) {
        return computeSha256Digest(signature.toByteArray());
    }

    /**
     * Computes the SHA256 digest of some data.
     * @param data The data.
     * @return The digest or null if an error occurs.
     */
    public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA256");
        } catch (NoSuchAlgorithmException e) {
            /* can't happen */
            return null;
        }

        messageDigest.update(data);

        final byte[] digest = messageDigest.digest();
        final int digestLength = digest.length;
        final int charCount = 2 * digestLength;

        final char[] chars = new char[charCount];
        for (int i = 0; i < digestLength; i++) {
            final int byteHex = digest[i] & 0xFF;
            chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
            chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
        }
        return new String(chars);
    }
}
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup;

import android.accounts.AccountManagerInternal;
import android.app.backup.BlobBackupHelper;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.LocalServices;

/**
 * Helper for handling backup of account manager specific state.
 */
public class AccountManagerBackupHelper extends BlobBackupHelper {
    private static final String TAG = "AccountsBackup";
    private static final boolean DEBUG = false;

    // current schema of the backup state blob
    private static final int STATE_VERSION = 1;

    // key under which the account access grant state blob is committed to backup
    private static final String KEY_ACCOUNT_ACCESS_GRANTS = "account_access_grants";

    public AccountManagerBackupHelper() {
        super(STATE_VERSION, KEY_ACCOUNT_ACCESS_GRANTS);
    }

    @Override
    protected byte[] getBackupPayload(String key) {
        AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class);
        if (DEBUG) {
            Slog.d(TAG, "Handling backup of " + key);
        }
        try {
            switch (key) {
                case KEY_ACCOUNT_ACCESS_GRANTS: {
                    return am.backupAccountAccessPermissions(UserHandle.USER_SYSTEM);
                }

                default: {
                    Slog.w(TAG, "Unexpected backup key " + key);
                }
            }
        } catch (Exception e) {
            Slog.e(TAG, "Unable to store payload " + key);
        }

        return new byte[0];
    }

    @Override
    protected void applyRestoredPayload(String key, byte[] payload) {
        AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class);
        if (DEBUG) {
            Slog.d(TAG, "Handling restore of " + key);
        }
        try {
            switch (key) {
                case KEY_ACCOUNT_ACCESS_GRANTS: {
                    am.restoreAccountAccessPermissions(payload, UserHandle.USER_SYSTEM);
                } break;

                default: {
                    Slog.w(TAG, "Unexpected restore key " + key);
                }
            }
        } catch (Exception e) {
            Slog.w(TAG, "Unable to restore key " + key);
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
    private static final String PERMISSION_HELPER = "permissions";
    private static final String USAGE_STATS_HELPER = "usage_stats";
    private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
    private static final String ACCOUNT_MANAGER_HELPER = "account_manager";

    // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
    // are also used in the full-backup file format, so must not change unless steps are
@@ -82,6 +83,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
        addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
        addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
        addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
        addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
        super.onBackup(oldState, data, newState);
    }

@@ -111,6 +113,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
        addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
        addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
        addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
        addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());

        try {
            super.onRestore(data, appVersionCode, newState);
+312 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.accounts;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper class for backup and restore of account access grants.
 */
public final class AccountManagerBackupHelper {
    private static final String TAG = "AccountManagerBackupHelper";

    private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour

    private static final String TAG_PERMISSIONS = "permissions";
    private static final String TAG_PERMISSION = "permission";
    private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
    private static final String ATTR_PACKAGE = "package";
    private static final String ATTR_DIGEST = "digest";

    private static final String ACCOUNT_ACCESS_GRANTS = ""
            + "SELECT " + AccountManagerService.ACCOUNTS_NAME + ", "
            + AccountManagerService.GRANTS_GRANTEE_UID
            + " FROM " + AccountManagerService.TABLE_ACCOUNTS
            + ", " + AccountManagerService.TABLE_GRANTS
            + " WHERE " + AccountManagerService.GRANTS_ACCOUNTS_ID
            + "=" + AccountManagerService.ACCOUNTS_ID;

    private final Object mLock = new Object();

    private final AccountManagerService mAccountManagerService;
    private final AccountManagerInternal mAccountManagerInternal;

    @GuardedBy("mLock")
    private List<PendingAppPermission> mRestorePendingAppPermissions;

    @GuardedBy("mLock")
    private RestorePackageMonitor mRestorePackageMonitor;

    @GuardedBy("mLock")
    private Runnable mRestoreCancelCommand;

    public AccountManagerBackupHelper(AccountManagerService accountManagerService,
            AccountManagerInternal accountManagerInternal) {
        mAccountManagerService = accountManagerService;
        mAccountManagerInternal = accountManagerInternal;
    }

    private final class PendingAppPermission {
        private final @NonNull String accountDigest;
        private final @NonNull String packageName;
        private final @NonNull String certDigest;
        private final @IntRange(from = 0) int userId;

        public PendingAppPermission(String accountDigest, String packageName,
                String certDigest, int userId) {
            this.accountDigest = accountDigest;
            this.packageName = packageName;
            this.certDigest = certDigest;
            this.userId = userId;
        }

        public boolean apply(PackageManager packageManager) {
            Account account = null;
            AccountManagerService.UserAccounts accounts = mAccountManagerService
                    .getUserAccounts(userId);
            synchronized (accounts.cacheLock) {
                for (Account[] accountsPerType : accounts.accountCache.values()) {
                    for (Account accountPerType : accountsPerType) {
                        if (accountDigest.equals(PackageUtils.computeSha256Digest(
                                accountPerType.name.getBytes()))) {
                            account = accountPerType;
                            break;
                        }
                    }
                    if (account != null) {
                        break;
                    }
                }
            }
            if (account == null) {
                return false;
            }
            final PackageInfo packageInfo;
            try {
                packageInfo = packageManager.getPackageInfoAsUser(packageName,
                        PackageManager.GET_SIGNATURES, userId);
            } catch (PackageManager.NameNotFoundException e) {
                return false;
            }
            String currentCertDigest = PackageUtils.computeCertSha256Digest(
                    packageInfo.signatures[0]);
            if (!certDigest.equals(currentCertDigest)) {
                return false;
            }
            final int uid = packageInfo.applicationInfo.uid;
            if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
                mAccountManagerService.grantAppPermission(account,
                        AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
            }
            return true;
        }
    }

    public byte[] backupAccountAccessPermissions(int userId) {
        final AccountManagerService.UserAccounts accounts = mAccountManagerService
                .getUserAccounts(userId);
        synchronized (accounts.cacheLock) {
            SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
            try (
                Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null);
            ) {
                if (cursor == null || !cursor.moveToFirst()) {
                    return null;
                }

                final int nameColumnIdx = cursor.getColumnIndex(
                        AccountManagerService.ACCOUNTS_NAME);
                final int uidColumnIdx = cursor.getColumnIndex(
                        AccountManagerService.GRANTS_GRANTEE_UID);

                ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
                try {
                    final XmlSerializer serializer = new FastXmlSerializer();
                    serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
                    serializer.startDocument(null, true);
                    serializer.startTag(null, TAG_PERMISSIONS);

                    PackageManager packageManager = mAccountManagerService.mContext
                            .getPackageManager();

                    do {
                        final String accountName = cursor.getString(nameColumnIdx);
                        final int uid = cursor.getInt(uidColumnIdx);

                        final String[] packageNames = packageManager.getPackagesForUid(uid);
                        if (packageNames == null) {
                            continue;
                        }

                        for (String packageName : packageNames) {
                            String digest = PackageUtils.computePackageCertSha256Digest(
                                    packageManager, packageName, userId);
                            if (digest != null) {
                                serializer.startTag(null, TAG_PERMISSION);
                                serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
                                        PackageUtils.computeSha256Digest(accountName.getBytes()));
                                serializer.attribute(null, ATTR_PACKAGE, packageName);
                                serializer.attribute(null, ATTR_DIGEST, digest);
                                serializer.endTag(null, TAG_PERMISSION);
                            }
                        }
                    } while (cursor.moveToNext());

                    serializer.endTag(null, TAG_PERMISSIONS);
                    serializer.endDocument();
                    serializer.flush();
                } catch (IOException e) {
                    Log.e(TAG, "Error backing up account access grants", e);
                    return null;
                }

                return dataStream.toByteArray();
            }
        }
    }

    public void restoreAccountAccessPermissions(byte[] data, int userId) {
        try {
            ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(dataStream, StandardCharsets.UTF_8.name());
            PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();

            final int permissionsOuterDepth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
                if (!TAG_PERMISSIONS.equals(parser.getName())) {
                    continue;
                }
                final int permissionOuterDepth = parser.getDepth();
                while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
                    if (!TAG_PERMISSION.equals(parser.getName())) {
                        continue;
                    }
                    String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
                    if (TextUtils.isEmpty(accountDigest)) {
                        XmlUtils.skipCurrentTag(parser);
                    }
                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
                    if (TextUtils.isEmpty(packageName)) {
                        XmlUtils.skipCurrentTag(parser);
                    }
                    String digest =  parser.getAttributeValue(null, ATTR_DIGEST);
                    if (TextUtils.isEmpty(digest)) {
                        XmlUtils.skipCurrentTag(parser);
                    }

                    PendingAppPermission pendingAppPermission = new PendingAppPermission(
                            accountDigest, packageName, digest, userId);

                    if (!pendingAppPermission.apply(packageManager)) {
                        synchronized (mLock) {
                            // Start watching before add pending to avoid a missed signal
                            if (mRestorePackageMonitor == null) {
                                mRestorePackageMonitor = new RestorePackageMonitor();
                                mRestorePackageMonitor.register(mAccountManagerService.mContext,
                                        mAccountManagerService.mHandler.getLooper(), true);
                            }
                            if (mRestorePendingAppPermissions == null) {
                                mRestorePendingAppPermissions = new ArrayList<>();
                            }
                            mRestorePendingAppPermissions.add(pendingAppPermission);
                        }
                    }
                }
            }

            // Make sure we eventually prune the in-memory pending restores
            mRestoreCancelCommand = new CancelRestoreCommand();
            mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
                    PENDING_RESTORE_TIMEOUT_MILLIS);
        } catch (XmlPullParserException | IOException e) {
            Log.e(TAG, "Error restoring app permissions", e);
        }
    }

    private final class RestorePackageMonitor extends PackageMonitor {
        @Override
        public void onPackageAdded(String packageName, int uid) {
            synchronized (mLock) {
                if (mRestorePendingAppPermissions == null) {
                    return;
                }
                if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
                    return;
                }
                final int count = mRestorePendingAppPermissions.size();
                for (int i = count - 1; i >= 0; i--) {
                    PendingAppPermission pendingAppPermission =
                            mRestorePendingAppPermissions.get(i);
                    if (!pendingAppPermission.packageName.equals(packageName)) {
                        continue;
                    }
                    if (pendingAppPermission.apply(
                            mAccountManagerService.mContext.getPackageManager())) {
                        mRestorePendingAppPermissions.remove(i);
                    }
                }
                if (mRestorePendingAppPermissions.isEmpty()
                        && mRestoreCancelCommand != null) {
                    mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
                    mRestoreCancelCommand.run();
                    mRestoreCancelCommand = null;
                }
            }
        }
    }

    private final class CancelRestoreCommand implements Runnable {
        @Override
        public void run() {
            synchronized (mLock) {
                mRestorePendingAppPermissions = null;
                if (mRestorePackageMonitor != null) {
                    mRestorePackageMonitor.unregister();
                    mRestorePackageMonitor = null;
                }
            }
        }
    }
}
Loading