From 58d09d0742e8c64a9cd94242ae011ca500010b9d Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Fri, 27 Jul 2018 15:29:42 +0100 Subject: [PATCH] Add tests for AccountManager and AndroidAccountManager. --- .../account/AndroidAccountManager.java | 8 +- .../account/AndroidAccountManagerTest.java | 78 +++++- .../bramble/api/account/AccountManager.java | 8 +- .../account/AccountManagerImplTest.java | 241 +++++++++++++++--- 4 files changed, 296 insertions(+), 39 deletions(-) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java index e19f2305f2..1ce36ed39f 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java @@ -69,12 +69,16 @@ class AndroidAccountManager extends AccountManagerImpl public void deleteAccount() { synchronized (stateChangeLock) { super.deleteAccount(); - SharedPreferences defaultPrefs = - PreferenceManager.getDefaultSharedPreferences(appContext); + SharedPreferences defaultPrefs = getDefaultSharedPreferences(); deleteAppData(prefs, defaultPrefs); } } + // Package access for testing + SharedPreferences getDefaultSharedPreferences() { + return PreferenceManager.getDefaultSharedPreferences(appContext); + } + // Locking: stateChangeLock private void deleteAppData(SharedPreferences... clear) { // Clear and commit shared preferences diff --git a/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java b/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java index 1922954a07..d283adceac 100644 --- a/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java +++ b/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.account; import android.app.Application; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.db.DatabaseConfig; @@ -25,37 +26,50 @@ import static org.briarproject.bramble.util.StringUtils.toHexString; public class AndroidAccountManagerTest extends BrambleMockTestCase { private final SharedPreferences prefs = - context.mock(SharedPreferences.class); + context.mock(SharedPreferences.class, "prefs"); + private final SharedPreferences defaultPrefs = + context.mock(SharedPreferences.class, "defaultPrefs"); private final DatabaseConfig databaseConfig = context.mock(DatabaseConfig.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final SharedPreferences.Editor editor = context.mock(SharedPreferences.Editor.class); private final Application app; + private final ApplicationInfo applicationInfo; private final String encryptedKeyHex = toHexString(getRandomBytes(123)); private final File testDir = getTestDirectory(); private final File keyDir = new File(testDir, "key"); private final File keyFile = new File(keyDir, "db.key"); private final File keyBackupFile = new File(keyDir, "db.key.bak"); + private final File dbDir = new File(testDir, "db"); private AndroidAccountManager accountManager; public AndroidAccountManagerTest() { context.setImposteriser(ClassImposteriser.INSTANCE); app = context.mock(Application.class); + applicationInfo = new ApplicationInfo(); + applicationInfo.dataDir = testDir.getAbsolutePath(); } @Before public void setUp() { context.checking(new Expectations() {{ + allowing(databaseConfig).getDatabaseDirectory(); + will(returnValue(dbDir)); allowing(databaseConfig).getDatabaseKeyDirectory(); will(returnValue(keyDir)); allowing(app).getApplicationContext(); will(returnValue(app)); }}); accountManager = new AndroidAccountManager(databaseConfig, crypto, - prefs, app); + prefs, app) { + @Override + SharedPreferences getDefaultSharedPreferences() { + return defaultPrefs; + } + }; } @Test @@ -74,12 +88,70 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase { assertFalse(keyFile.exists()); assertFalse(keyBackupFile.exists()); - assertEquals(encryptedKeyHex, accountManager.loadEncryptedDatabaseKey()); + assertEquals(encryptedKeyHex, + accountManager.loadEncryptedDatabaseKey()); assertTrue(keyFile.exists()); assertTrue(keyBackupFile.exists()); } + @Test + public void testDeleteAccountClearsSharedPrefsAndDeletesFiles() + throws Exception { + // Directories 'lib' and 'shared_prefs' should be spared + File libDir = new File(testDir, "lib"); + File libFile = new File(libDir, "file"); + File sharedPrefsDir = new File(testDir, "shared_prefs"); + File sharedPrefsFile = new File(sharedPrefsDir, "file"); + // Directory 'cache' should be emptied + File cacheDir = new File(testDir, "cache"); + File cacheFile = new File(cacheDir, "file"); + // Other directories should be deleted + File potatoDir = new File(testDir, ".potato"); + File potatoFile = new File(potatoDir, "file"); + + context.checking(new Expectations() {{ + oneOf(prefs).edit(); + will(returnValue(editor)); + oneOf(editor).clear(); + will(returnValue(editor)); + oneOf(editor).commit(); + will(returnValue(true)); + oneOf(defaultPrefs).edit(); + will(returnValue(editor)); + oneOf(editor).clear(); + will(returnValue(editor)); + oneOf(editor).commit(); + will(returnValue(true)); + oneOf(app).getApplicationInfo(); + will(returnValue(applicationInfo)); + }}); + + assertTrue(dbDir.mkdirs()); + assertTrue(keyDir.mkdirs()); + assertTrue(libDir.mkdirs()); + assertTrue(libFile.createNewFile()); + assertTrue(sharedPrefsDir.mkdirs()); + assertTrue(sharedPrefsFile.createNewFile()); + assertTrue(cacheDir.mkdirs()); + assertTrue(cacheFile.createNewFile()); + assertTrue(potatoDir.mkdirs()); + assertTrue(potatoFile.createNewFile()); + + accountManager.deleteAccount(); + + assertFalse(dbDir.exists()); + assertFalse(keyDir.exists()); + assertTrue(libDir.exists()); + assertTrue(libFile.exists()); + assertTrue(sharedPrefsDir.exists()); + assertTrue(sharedPrefsFile.exists()); + assertTrue(cacheDir.exists()); + assertFalse(cacheFile.exists()); + assertFalse(potatoDir.exists()); + assertFalse(potatoFile.exists()); + } + @After public void tearDown() { deleteTestDirectory(testDir); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java index e2784c1458..5b475ae636 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java @@ -12,7 +12,7 @@ public interface AccountManager { * Returns true if the manager has the database key. This will be false * before {@link #createAccount(String)} or {@link #signIn(String)} has * been called, and true after {@link #createAccount(String)} or - * {@link #signIn(String)} has returned, until the process exits. + * {@link #signIn(String)} has returned true, until the process exits. */ boolean hasDatabaseKey(); @@ -20,14 +20,14 @@ public interface AccountManager { * Returns the database key if the manager has it. This will be null * before {@link #createAccount(String)} or {@link #signIn(String)} has * been called, and non-null after {@link #createAccount(String)} or - * {@link #signIn(String)} has returned, until the process exits. + * {@link #signIn(String)} has returned true, until the process exits. */ @Nullable SecretKey getDatabaseKey(); /** - * Returns true if the encrypted database key can be loaded from disk and - * the database directory exists. + * Returns true if the encrypted database key can be loaded from disk, and + * the database directory exists and is a directory. */ boolean accountExists(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java index 6e43e23f8f..998fa1c5e4 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.account; import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; @@ -22,9 +23,12 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.util.StringUtils.toHexString; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class AccountManagerImplTest extends BrambleMockTestCase { @@ -32,10 +36,15 @@ public class AccountManagerImplTest extends BrambleMockTestCase { context.mock(DatabaseConfig.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class); + private final SecretKey key = getSecretKey(); private final byte[] encryptedKey = getRandomBytes(123); private final String encryptedKeyHex = toHexString(encryptedKey); - private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123)); + private final byte[] newEncryptedKey = getRandomBytes(123); + private final String newEncryptedKeyHex = toHexString(newEncryptedKey); + private final String password = "some.password"; + private final String newPassword = "some.new.password"; private final File testDir = getTestDirectory(); + private final File dbDir = new File(testDir, "db"); private final File keyDir = new File(testDir, "key"); private final File keyFile = new File(keyDir, "db.key"); private final File keyBackupFile = new File(keyDir, "db.key.bak"); @@ -45,56 +54,115 @@ public class AccountManagerImplTest extends BrambleMockTestCase { @Before public void setUp() { context.checking(new Expectations() {{ + allowing(databaseConfig).getDatabaseDirectory(); + will(returnValue(dbDir)); allowing(databaseConfig).getDatabaseKeyDirectory(); will(returnValue(keyDir)); }}); - assertTrue(keyDir.mkdirs()); + accountManager = new AccountManagerImpl(databaseConfig, crypto); + + assertFalse(keyFile.exists()); + assertFalse(keyBackupFile.exists()); } @Test - public void testDbKeyIsLoadedFromPrimaryFile() throws Exception { + public void testCreatingAccountStoresDbKey() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).generateSecretKey(); + will(returnValue(key)); + oneOf(crypto).encryptWithPassword(key.getBytes(), password); + will(returnValue(encryptedKey)); + }}); + + accountManager.createAccount(password); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + + @Test + public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() { + assertFalse(accountManager.signIn(password)); + assertFalse(accountManager.hasDatabaseKey()); + assertFalse(keyFile.exists()); assertFalse(keyBackupFile.exists()); + } + + @Test + public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).decryptWithPassword(encryptedKey, password); + will(returnValue(null)); + }}); storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); - assertTrue(keyFile.exists()); - assertFalse(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + + assertFalse(accountManager.signIn(password)); + assertFalse(accountManager.hasDatabaseKey()); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + + @Test + public void testSignInReturnsTrueIfPasswordIsRight() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).decryptWithPassword(encryptedKey, password); + will(returnValue(key.getBytes())); + }}); + + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + + assertTrue(accountManager.signIn(password)); + assertTrue(accountManager.hasDatabaseKey()); + SecretKey decrypted = accountManager.getDatabaseKey(); + assertNotNull(decrypted); + assertArrayEquals(key.getBytes(), decrypted.getBytes()); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + + @Test + public void testDbKeyIsLoadedFromPrimaryFile() throws Exception { + storeDatabaseKey(keyFile, encryptedKeyHex); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertFalse(keyBackupFile.exists()); assertEquals(encryptedKeyHex, accountManager.loadEncryptedDatabaseKey()); - assertTrue(keyFile.exists()); - assertFalse(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertFalse(keyBackupFile.exists()); } @Test public void testDbKeyIsLoadedFromBackupFile() throws Exception { - assertFalse(keyFile.exists()); - assertFalse(keyBackupFile.exists()); - storeDatabaseKey(keyBackupFile, encryptedKeyHex); assertFalse(keyFile.exists()); - assertTrue(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, accountManager.loadEncryptedDatabaseKey()); assertFalse(keyFile.exists()); - assertTrue(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); } @Test public void testDbKeyIsNullIfNotFound() { - assertFalse(keyFile.exists()); - assertFalse(keyBackupFile.exists()); - assertNull(accountManager.loadEncryptedDatabaseKey()); assertFalse(keyFile.exists()); @@ -103,43 +171,156 @@ public class AccountManagerImplTest extends BrambleMockTestCase { @Test public void testStoringDbKeyOverwritesPrimary() throws Exception { - assertFalse(keyFile.exists()); + storeDatabaseKey(keyFile, encryptedKeyHex); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertFalse(keyBackupFile.exists()); - storeDatabaseKey(keyFile, oldEncryptedKeyHex); + assertTrue(accountManager.storeEncryptedDatabaseKey( + newEncryptedKeyHex)); + + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } - assertTrue(keyFile.exists()); + @Test + public void testStoringDbKeyOverwritesBackup() throws Exception { + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertFalse(keyFile.exists()); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + + assertTrue(accountManager.storeEncryptedDatabaseKey( + newEncryptedKeyHex)); + + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + + @Test + public void testAccountExistsReturnsFalseIfDbKeyCannotBeLoaded() { + assertFalse(accountManager.accountExists()); + + assertFalse(keyFile.exists()); assertFalse(keyBackupFile.exists()); - assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile)); + } + + @Test + public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist() + throws Exception { + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); - assertTrue(accountManager.storeEncryptedDatabaseKey(encryptedKeyHex)); + assertFalse(dbDir.exists()); + + assertFalse(accountManager.accountExists()); - assertTrue(keyFile.exists()); - assertTrue(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + assertFalse(dbDir.exists()); } @Test - public void testStoringDbKeyOverwritesBackup() throws Exception { + public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory() + throws Exception { + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertTrue(dbDir.createNewFile()); + assertFalse(dbDir.isDirectory()); + + assertFalse(accountManager.accountExists()); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + assertTrue(dbDir.exists()); + assertFalse(dbDir.isDirectory()); + } + + @Test + public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory() + throws Exception { + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertTrue(dbDir.mkdirs()); + assertTrue(dbDir.isDirectory()); + + assertTrue(accountManager.accountExists()); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + assertTrue(dbDir.exists()); + assertTrue(dbDir.isDirectory()); + } + + @Test + public void testCreateAccountStoresDbKey() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).generateSecretKey(); + will(returnValue(key)); + oneOf(crypto).encryptWithPassword(key.getBytes(), password); + will(returnValue(encryptedKey)); + }}); + + assertFalse(accountManager.hasDatabaseKey()); + + assertTrue(accountManager.createAccount(password)); + + assertTrue(accountManager.hasDatabaseKey()); + SecretKey dbKey = accountManager.getDatabaseKey(); + assertNotNull(dbKey); + assertArrayEquals(key.getBytes(), dbKey.getBytes()); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + + @Test + public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() { + assertFalse(accountManager.changePassword(password, newPassword)); + assertFalse(keyFile.exists()); assertFalse(keyBackupFile.exists()); + } - storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex); + @Test + public void testChangePasswordReturnsFalseIfPasswordIsWrong() + throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).decryptWithPassword(encryptedKey, password); + will(returnValue(null)); + }}); - assertFalse(keyFile.exists()); - assertTrue(keyBackupFile.exists()); - assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); - assertTrue(accountManager.storeEncryptedDatabaseKey(encryptedKeyHex)); + assertFalse(accountManager.changePassword(password, newPassword)); - assertTrue(keyFile.exists()); - assertTrue(keyBackupFile.exists()); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); } + @Test + public void testChangePasswordReturnsTrueIfPasswordIsRight() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).decryptWithPassword(encryptedKey, password); + will(returnValue(key.getBytes())); + oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword); + will(returnValue(newEncryptedKey)); + }}); + + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertTrue(accountManager.changePassword(password, newPassword)); + + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + private void storeDatabaseKey(File f, String hex) throws IOException { + f.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(f); out.write(hex.getBytes("UTF-8")); out.flush(); -- GitLab