KeyChain: Upgrade keys database
A new table was added to KeyChain's grants.db, storing the
user-visibilty state of each alias KeyChain manages.
Formerly it was not created during an upgrade, which led to existing
keys being classified as non-user-selectable and essentially not usable.
Now, during upgrade, the table for user-visibility is created and the
existing aliases are all set as user-selectable in it.
Test: m -j RunKeyChainRoboTests
Bug: 73898958
Change-Id: I3b92a957f4e949c13363769ece531af438895ff9
diff --git a/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
index 68b28e3..a1d4990 100644
--- a/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
+++ b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
@@ -19,7 +19,11 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.ContentValues;
+import android.content.Context;
import android.content.pm.PackageManager;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import com.android.keychain.TestConfig;
import org.junit.Assert;
import org.junit.Before;
@@ -37,6 +41,12 @@
private static final String DUMMY_ALIAS2 = "another_dummy_alias";
private static final int DUMMY_UID = 1000;
private static final int DUMMY_UID2 = 1001;
+ // Constants duplicated from GrantsDatabase to make sure the upgrade tests catch if the
+ // name of one of the fields in the DB changes.
+ static final String DATABASE_NAME = "grants.db";
+ static final String TABLE_GRANTS = "grants";
+ static final String GRANTS_ALIAS = "alias";
+ static final String GRANTS_GRANTEE_UID = "uid";
private GrantsDatabase mGrantsDB;
@@ -107,7 +117,7 @@
public void testPurgeOldGrantsDoesNotDeleteGrantsForExistingPackages() {
mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
PackageManager pm = mock(PackageManager.class);
- when(pm.getPackagesForUid(DUMMY_UID)).thenReturn(new String[]{"p"});
+ when(pm.getPackagesForUid(DUMMY_UID)).thenReturn(new String[] {"p"});
mGrantsDB.purgeOldGrants(pm);
Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
}
@@ -146,4 +156,57 @@
mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true);
Assert.assertTrue(mGrantsDB.isUserSelectable(DUMMY_ALIAS));
}
+
+ private class V1DatabaseHelper extends SQLiteOpenHelper {
+ public V1DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null /* CursorFactory */, 1);
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ db.execSQL(
+ "CREATE TABLE "
+ + TABLE_GRANTS
+ + " ( "
+ + GRANTS_ALIAS
+ + " STRING NOT NULL, "
+ + GRANTS_GRANTEE_UID
+ + " INTEGER NOT NULL, "
+ + "UNIQUE ("
+ + GRANTS_ALIAS
+ + ","
+ + GRANTS_GRANTEE_UID
+ + "))");
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
+ throw new IllegalStateException("Existing DB must be dropped first.");
+ }
+ }
+
+ @Test
+ public void testUpgradeDatabase() {
+ // Close old DB
+ mGrantsDB.destroy();
+ // Create a new, V1 database.
+ Context context = RuntimeEnvironment.application;
+ context.deleteDatabase(DATABASE_NAME);
+ V1DatabaseHelper v1DBHelper = new V1DatabaseHelper(context);
+ // Fill it up with a few records
+ final SQLiteDatabase db = v1DBHelper.getWritableDatabase();
+ String[] aliases = {"alias-1", "alias-2", "alias-3"};
+ for (String alias : aliases) {
+ final ContentValues values = new ContentValues();
+ values.put(GRANTS_ALIAS, alias);
+ values.put(GRANTS_GRANTEE_UID, 123456);
+ db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
+ }
+
+ // Test that the aliases were made user-selectable during the upgrade.
+ mGrantsDB = new GrantsDatabase(RuntimeEnvironment.application);
+ for (String alias : aliases) {
+ Assert.assertTrue(mGrantsDB.isUserSelectable(alias));
+ }
+ }
}
diff --git a/src/com/android/keychain/internal/GrantsDatabase.java b/src/com/android/keychain/internal/GrantsDatabase.java
index 1e333c1..ae11505 100644
--- a/src/com/android/keychain/internal/GrantsDatabase.java
+++ b/src/com/android/keychain/internal/GrantsDatabase.java
@@ -29,7 +29,7 @@
private static final String TAG = "KeyChain";
private static final String DATABASE_NAME = "grants.db";
- private static final int DATABASE_VERSION = 1;
+ private static final int DATABASE_VERSION = 2;
private static final String TABLE_GRANTS = "grants";
private static final String GRANTS_ALIAS = "alias";
private static final String GRANTS_GRANTEE_UID = "uid";
@@ -62,6 +62,20 @@
super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
}
+ void createSelectableTable(final SQLiteDatabase db) {
+ db.execSQL(
+ "CREATE TABLE "
+ + TABLE_SELECTABLE
+ + " ( "
+ + GRANTS_ALIAS
+ + " STRING NOT NULL, "
+ + SELECTABLE_IS_SELECTABLE
+ + " STRING NOT NULL, "
+ + "UNIQUE ("
+ + GRANTS_ALIAS
+ + "))");
+ }
+
@Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL(
@@ -78,26 +92,39 @@
+ GRANTS_GRANTEE_UID
+ "))");
- db.execSQL(
- "CREATE TABLE "
- + TABLE_SELECTABLE
- + " ( "
- + GRANTS_ALIAS
- + " STRING NOT NULL, "
- + SELECTABLE_IS_SELECTABLE
- + " STRING NOT NULL, "
- + "UNIQUE ("
- + GRANTS_ALIAS
- + "))");
+ createSelectableTable(db);
}
@Override
public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
- Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
+ Log.w(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
if (oldVersion == 1) {
- // the first upgrade step goes here
+ // Version 1 of the database does not have the 'userselectable' table, meaning
+ // upgraded keys could not be selected by users.
+ // The upgrade from version 1 to 2 consists of creating the 'userselectable'
+ // table and adding all existing keys as user-selectable ones into that table.
oldVersion++;
+ createSelectableTable(db);
+
+ try (Cursor cursor =
+ db.query(
+ TABLE_GRANTS,
+ new String[] {GRANTS_ALIAS},
+ null,
+ null,
+ GRANTS_ALIAS,
+ null,
+ null)) {
+
+ while ((cursor != null) && (cursor.moveToNext())) {
+ final String alias = cursor.getString(0);
+ final ContentValues values = new ContentValues();
+ values.put(GRANTS_ALIAS, alias);
+ values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(true));
+ db.replace(TABLE_SELECTABLE, null, values);
+ }
+ }
}
}
}