1 /* 2 * Copyright (C) 2017 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.keychain.internal; 18 19 import static org.mockito.Mockito.mock; 20 import static org.mockito.Mockito.when; 21 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.pm.PackageManager; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteOpenHelper; 27 import org.junit.Assert; 28 import org.junit.Before; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.robolectric.RobolectricTestRunner; 32 import org.robolectric.RuntimeEnvironment; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 38 /** Unit tests for {@link com.android.keychain.internal.GrantsDatabase}. */ 39 @RunWith(RobolectricTestRunner.class) 40 public final class GrantsDatabaseTest { 41 private static final String DUMMY_ALIAS = "dummy_alias"; 42 private static final String DUMMY_ALIAS2 = "another_dummy_alias"; 43 private static final String EXISTING_ALIAS = "existing_alias_1"; 44 private static final int DUMMY_UID = 1000; 45 private static final int DUMMY_UID2 = 1001; 46 // Constants duplicated from GrantsDatabase to make sure the upgrade tests catch if the 47 // name of one of the fields in the DB changes. 48 private static final String DATABASE_NAME = "grants.db"; 49 private static final String TABLE_GRANTS = "grants"; 50 private static final String GRANTS_ALIAS = "alias"; 51 private static final String GRANTS_GRANTEE_UID = "uid"; 52 private static final String TABLE_SELECTABLE = "userselectable"; 53 private static final String SELECTABLE_IS_SELECTABLE = "is_selectable"; 54 55 private GrantsDatabase mGrantsDB; 56 private ExistingKeysProvider mKeysProvider; 57 createExistingKeysProvider( String[] keyAliases, String... additionalAliases)58 private ExistingKeysProvider createExistingKeysProvider( 59 String[] keyAliases, String... additionalAliases) { 60 ArrayList<String> allAliases = new ArrayList(Arrays.asList(keyAliases)); 61 allAliases.addAll(Arrays.asList(additionalAliases)); 62 return new ExistingKeysProvider() { 63 public List<String> getExistingKeyAliases() { 64 return allAliases; 65 } 66 }; 67 } 68 69 @Before 70 public void setUp() { 71 mKeysProvider = createExistingKeysProvider(new String[] {EXISTING_ALIAS}); 72 mGrantsDB = new GrantsDatabase(RuntimeEnvironment.application, mKeysProvider); 73 } 74 75 @Test 76 public void testSetGrant_notMixingUIDs() { 77 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 78 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS)); 79 } 80 81 @Test 82 public void testSetGrant_notMixingAliases() { 83 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 84 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS2)); 85 } 86 87 @Test 88 public void testSetGrantTrue() { 89 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 90 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 91 Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 92 } 93 94 @Test 95 public void testSetGrantFalse() { 96 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, false); 97 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 98 } 99 100 @Test 101 public void testSetGrantTrueThenFalse() { 102 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 103 Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 104 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, false); 105 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 106 } 107 108 @Test 109 public void testRemoveAliasInformation() { 110 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 111 mGrantsDB.setGrant(DUMMY_UID2, DUMMY_ALIAS, true); 112 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true); 113 Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 114 mGrantsDB.removeAliasInformation(DUMMY_ALIAS); 115 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 116 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS)); 117 Assert.assertFalse(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 118 } 119 120 @Test 121 public void testRemoveAllAliasesInformation() { 122 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 123 mGrantsDB.setGrant(DUMMY_UID2, DUMMY_ALIAS, true); 124 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS2, true); 125 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true); 126 mGrantsDB.removeAllAliasesInformation(); 127 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 128 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS)); 129 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS2)); 130 Assert.assertFalse(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 131 } 132 133 @Test 134 public void testPurgeOldGrantsDoesNotDeleteGrantsForExistingPackages() { 135 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 136 PackageManager pm = mock(PackageManager.class); 137 when(pm.getPackagesForUid(DUMMY_UID)).thenReturn(new String[] {"p"}); 138 mGrantsDB.purgeOldGrants(pm); 139 Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 140 } 141 142 @Test 143 public void testPurgeOldGrantsPurgesAllNonExistingPackages() { 144 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 145 mGrantsDB.setGrant(DUMMY_UID2, DUMMY_ALIAS, true); 146 PackageManager pm = mock(PackageManager.class); 147 when(pm.getPackagesForUid(DUMMY_UID)).thenReturn(null); 148 when(pm.getPackagesForUid(DUMMY_UID2)).thenReturn(null); 149 mGrantsDB.purgeOldGrants(pm); 150 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS)); 151 Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS)); 152 } 153 154 @Test 155 public void testPurgeOldGrantsWorksOnEmptyDatabase() { 156 // Check that NPE is not thrown. 157 mGrantsDB.purgeOldGrants(null); 158 } 159 160 @Test 161 public void testIsUserSelectable() { 162 Assert.assertFalse(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 163 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true); 164 Assert.assertTrue(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 165 } 166 167 @Test 168 public void testSetUserSelectable() { 169 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true); 170 Assert.assertTrue(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 171 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, false); 172 Assert.assertFalse(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 173 mGrantsDB.setIsUserSelectable(DUMMY_ALIAS, true); 174 Assert.assertTrue(mGrantsDB.isUserSelectable(DUMMY_ALIAS)); 175 } 176 177 private abstract class BaseGrantsDatabaseHelper extends SQLiteOpenHelper { 178 private final boolean mCreateUserSelectableTable; 179 180 public BaseGrantsDatabaseHelper( 181 Context context, int dbVersion, boolean createUserSelectableTable) { 182 super(context, DATABASE_NAME, null /* CursorFactory */, dbVersion); 183 mCreateUserSelectableTable = createUserSelectableTable; 184 } 185 186 void createUserSelectableTable(final SQLiteDatabase db) { 187 db.execSQL( 188 "CREATE TABLE " 189 + TABLE_SELECTABLE 190 + " ( " 191 + GRANTS_ALIAS 192 + " STRING NOT NULL, " 193 + SELECTABLE_IS_SELECTABLE 194 + " STRING NOT NULL, " 195 + "UNIQUE (" 196 + GRANTS_ALIAS 197 + "))"); 198 } 199 200 @Override 201 public void onCreate(final SQLiteDatabase db) { 202 db.execSQL( 203 "CREATE TABLE " 204 + TABLE_GRANTS 205 + " ( " 206 + GRANTS_ALIAS 207 + " STRING NOT NULL, " 208 + GRANTS_GRANTEE_UID 209 + " INTEGER NOT NULL, " 210 + "UNIQUE (" 211 + GRANTS_ALIAS 212 + "," 213 + GRANTS_GRANTEE_UID 214 + "))"); 215 216 if (mCreateUserSelectableTable) { 217 createUserSelectableTable(db); 218 } 219 } 220 221 @Override 222 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) { 223 throw new IllegalStateException("Existing DB must be dropped first."); 224 } 225 226 public void insertIntoGrantsTable(final SQLiteDatabase db, String alias, int uid) { 227 final ContentValues values = new ContentValues(); 228 values.put(GRANTS_ALIAS, alias); 229 values.put(GRANTS_GRANTEE_UID, uid); 230 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values); 231 } 232 233 public void insertIntoSelectableTable( 234 final SQLiteDatabase db, String alias, boolean isSelectable) { 235 final ContentValues values = new ContentValues(); 236 values.put(GRANTS_ALIAS, alias); 237 values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(isSelectable)); 238 db.insert(TABLE_SELECTABLE, null, values); 239 } 240 } 241 242 private class V1DatabaseHelper extends BaseGrantsDatabaseHelper { 243 public V1DatabaseHelper(Context context) { 244 super(context, 1, false); 245 } 246 } 247 248 private class V2DatabaseHelper extends BaseGrantsDatabaseHelper { 249 public V2DatabaseHelper(Context context) { 250 super(context, 2, true); 251 } 252 } 253 254 private class IncorrectlyVersionedV2DatabaseHelper extends BaseGrantsDatabaseHelper { 255 public IncorrectlyVersionedV2DatabaseHelper(Context context) { 256 super(context, 1, true); 257 } 258 } 259 260 @Test 261 public void testUpgradeDatabase() { 262 // Close old DB 263 mGrantsDB.destroy(); 264 // Create a new, V1 database. 265 Context context = RuntimeEnvironment.application; 266 context.deleteDatabase(DATABASE_NAME); 267 V1DatabaseHelper v1DBHelper = new V1DatabaseHelper(context); 268 // Fill it up with a few records 269 final SQLiteDatabase db = v1DBHelper.getWritableDatabase(); 270 String[] aliases = {"alias-1", "alias-2", "alias-3"}; 271 for (String alias : aliases) { 272 v1DBHelper.insertIntoGrantsTable(db, alias, 123456); 273 } 274 275 // Test that the aliases were made user-selectable during the upgrade. 276 mGrantsDB = new GrantsDatabase( 277 RuntimeEnvironment.application, createExistingKeysProvider(aliases, EXISTING_ALIAS)); 278 for (String alias : aliases) { 279 Assert.assertTrue(mGrantsDB.isUserSelectable(alias)); 280 } 281 Assert.assertTrue(mGrantsDB.isUserSelectable(EXISTING_ALIAS)); 282 } 283 284 @Test 285 public void testSelectabilityInV2DatabaseNotChanged() { 286 // Close old DB 287 mGrantsDB.destroy(); 288 Context context = RuntimeEnvironment.application; 289 context.deleteDatabase(DATABASE_NAME); 290 // Create a new, V2 database. 291 V2DatabaseHelper v2DBHelper = new V2DatabaseHelper(context); 292 // Fill it up with a few records 293 final SQLiteDatabase db = v2DBHelper.getWritableDatabase(); 294 String[] aliases = {"alias-1", "alias-2", "alias-3"}; 295 for (String alias : aliases) { 296 v2DBHelper.insertIntoGrantsTable(db, alias, 123456); 297 v2DBHelper.insertIntoSelectableTable(db, alias, false); 298 } 299 String selectableAlias = "alias-selectable-1"; 300 v2DBHelper.insertIntoGrantsTable(db, selectableAlias, 123457); 301 v2DBHelper.insertIntoSelectableTable(db, selectableAlias, true); 302 303 // Test that the aliases were made user-selectable during the upgrade. 304 mGrantsDB = new GrantsDatabase( 305 RuntimeEnvironment.application, 306 createExistingKeysProvider(aliases, selectableAlias, EXISTING_ALIAS)); 307 for (String alias : aliases) { 308 Assert.assertFalse(mGrantsDB.isUserSelectable(alias)); 309 } 310 Assert.assertTrue(mGrantsDB.isUserSelectable(selectableAlias)); 311 // No upgrade is taking place, this key should not be user-selectable. 312 Assert.assertFalse(mGrantsDB.isUserSelectable(EXISTING_ALIAS)); 313 } 314 315 @Test 316 public void testV1AndAHalfDBUpgradedCorrectly() { 317 // Close old DB 318 mGrantsDB.destroy(); 319 Context context = RuntimeEnvironment.application; 320 context.deleteDatabase(DATABASE_NAME); 321 // Create a new, V2 database that's incorrectly versioned as v1. 322 IncorrectlyVersionedV2DatabaseHelper dbHelper = 323 new IncorrectlyVersionedV2DatabaseHelper(context); 324 // Fill it up with a few records 325 final SQLiteDatabase db = dbHelper.getWritableDatabase(); 326 String[] aliases = {"alias-1", "alias-2", "alias-3"}; 327 for (String alias : aliases) { 328 dbHelper.insertIntoGrantsTable(db, alias, 123456); 329 dbHelper.insertIntoSelectableTable(db, alias, false); 330 } 331 332 // Insert one alias explicitly selectable 333 String selectableAlias = "alias-selectable-1"; 334 dbHelper.insertIntoGrantsTable(db, selectableAlias, 123456); 335 dbHelper.insertIntoSelectableTable(db, selectableAlias, true); 336 337 // Insert one alias without explicitl user-selectability, which should 338 // default to true when upgrading from V1 to V2. 339 String defaultSelectableAlias = "alias-selectable-2"; 340 dbHelper.insertIntoGrantsTable(db, defaultSelectableAlias, 123456); 341 342 // Test that the aliases were made user-selectable during the upgrade. 343 mGrantsDB = new GrantsDatabase( 344 RuntimeEnvironment.application, 345 createExistingKeysProvider( 346 aliases, selectableAlias, defaultSelectableAlias, EXISTING_ALIAS)); 347 for (String alias : aliases) { 348 Assert.assertFalse(mGrantsDB.isUserSelectable(alias)); 349 } 350 Assert.assertTrue(mGrantsDB.isUserSelectable(selectableAlias)); 351 Assert.assertTrue(mGrantsDB.isUserSelectable(defaultSelectableAlias)); 352 Assert.assertTrue(mGrantsDB.isUserSelectable(EXISTING_ALIAS)); 353 } 354 355 @Test 356 public void testCreateFromEmptyWithExistingAliases() { 357 // Close old DB 358 mGrantsDB.destroy(); 359 // Create a new, V1 database. 360 Context context = RuntimeEnvironment.application; 361 context.deleteDatabase(DATABASE_NAME); 362 String[] aliases = {"existing-1", "existing-2", "existing-3"}; 363 364 // Test that the aliases were made user-selectable during the upgrade. 365 mGrantsDB = new GrantsDatabase( 366 RuntimeEnvironment.application, 367 createExistingKeysProvider(aliases)); 368 for (String alias : aliases) { 369 Assert.assertTrue(mGrantsDB.isUserSelectable(alias)); 370 } 371 } 372 373 @Test 374 public void testGetGrants_empty() { 375 Assert.assertArrayEquals(new int[0], mGrantsDB.getGrants(DUMMY_ALIAS)); 376 } 377 378 @Test 379 public void testGetGrants_nonEmpty() { 380 mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true); 381 mGrantsDB.setGrant(DUMMY_UID + 1, DUMMY_ALIAS, true); 382 383 Assert.assertArrayEquals( 384 new int[]{DUMMY_UID, DUMMY_UID + 1}, mGrantsDB.getGrants(DUMMY_ALIAS)); 385 } 386 } 387