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