diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java index 4250ab95722cfd1c107407e25824fd2f5172e83a..54de6261bec9bc3a1e254fc9a8012a0b569e15a8 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java @@ -82,6 +82,11 @@ public interface LifecycleManager { */ void registerForShutdown(ExecutorService e); + /** + * Check if the mailbox has been set up already. + */ + boolean hasBeenSetUp(); + /** * Opens the {@link Database} using the given key and starts any * registered {@link Service Services}. diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt index ab7a5703f57429de8b0df195699c3cd5fb59e58c..431dcc1486cd51184732ee2cee4b8b57e3c79f6b 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt @@ -35,6 +35,7 @@ import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS +import org.briarproject.mailbox.core.setup.SetupManager import org.briarproject.mailbox.core.setup.WipeManager import org.briarproject.mailbox.core.util.LogUtils.info import org.briarproject.mailbox.core.util.LogUtils.logDuration @@ -54,6 +55,7 @@ import kotlin.concurrent.thread @ThreadSafe internal class LifecycleManagerImpl @Inject constructor( private val db: Database, + private val setupManager: SetupManager, private val wipeManager: WipeManager, ) : LifecycleManager, MigrationListener { @@ -96,12 +98,39 @@ internal class LifecycleManagerImpl @Inject constructor( executors.add(e) } + @GuardedBy("startStopWipeSemaphore") + override fun hasBeenSetUp(): Boolean { + try { + startStopWipeSemaphore.acquire() + } catch (e: InterruptedException) { + LOG.warn("Interrupted while checking whether mailbox has been set up") + return false + } + return try { + if (state.value == RUNNING) { + // while RUNNING, we can just ask the database + db.read { txn -> setupManager.getOwnerToken(txn) != null } + } else { + // before [startServices] has been called or after [stopServices], + // open and close database for looking up owner token + db.open(null) + try { + db.read { txn -> setupManager.getOwnerToken(txn) != null } + } finally { + db.close() + } + } + } finally { + startStopWipeSemaphore.release() + } + } + @GuardedBy("startStopWipeSemaphore") override fun startServices(): StartResult { try { startStopWipeSemaphore.acquire() } catch (e: InterruptedException) { - LOG.warn("Interrupted while waiting to stop services") + LOG.warn("Interrupted while waiting to start services") return SERVICE_ERROR } if (!state.compareAndSet(NOT_STARTED, STARTING)) { diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt index 10afd983ebd1403e75c3a426035ff35d0a80efdb..da2538c06424327a21b5adfb9ca0f006a12e5c10 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt @@ -30,7 +30,7 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) { protected lateinit var testComponent: TestComponent protected val db by lazy { testComponent.getDatabase() } - private val lifecycleManager by lazy { testComponent.getLifecycleManager() } + protected val lifecycleManager by lazy { testComponent.getLifecycleManager() } protected val metadataManager by lazy { testComponent.getMetadataManager() } protected val httpClient = HttpClient(CIO) { expectSuccess = false // prevents exceptions on non-success responses diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeRouteManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeRouteManagerTest.kt index 6991e98dd4dbc0544bae03f3327974455a46f447..8c63f04f293a49823ab3d1b41967747a0b9677ce 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeRouteManagerTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeRouteManagerTest.kt @@ -7,6 +7,8 @@ import kotlinx.coroutines.runBlocking import org.briarproject.mailbox.core.server.IntegrationTest import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue class WipeRouteManagerTest : IntegrationTest() { @@ -28,4 +30,26 @@ class WipeRouteManagerTest : IntegrationTest() { assertEquals(HttpStatusCode.Unauthorized, response2.status) } + @Test + fun `set up state is consistent`() = runBlocking { + assertFalse(lifecycleManager.hasBeenSetUp()) + addOwnerToken() + assertTrue(lifecycleManager.hasBeenSetUp()) + addContact(contact1) + assertTrue(lifecycleManager.hasBeenSetUp()) + + // Unauthorized with random token + val response1 = httpClient.delete<HttpResponse>("$baseUrl/") { + authenticateWithToken(token) + } + assertEquals(HttpStatusCode.Unauthorized, response1.status) + + // Unauthorized with contact's token + val response2 = httpClient.delete<HttpResponse>("$baseUrl/") { + authenticateWithToken(contact1.token) + } + assertEquals(HttpStatusCode.Unauthorized, response2.status) + assertTrue(lifecycleManager.hasBeenSetUp()) + } + } diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipingWipeRouteManagerTest2.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipingWipeRouteManagerTest2.kt new file mode 100644 index 0000000000000000000000000000000000000000..3835358552ae30da2776a2096a2e8b9a13a36a9a --- /dev/null +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipingWipeRouteManagerTest2.kt @@ -0,0 +1,79 @@ +package org.briarproject.mailbox.core.setup + +import io.ktor.client.request.delete +import io.ktor.client.request.post +import io.ktor.client.statement.HttpResponse +import io.ktor.http.HttpStatusCode +import kotlinx.coroutines.runBlocking +import org.briarproject.mailbox.core.db.DbException +import org.briarproject.mailbox.core.server.IntegrationTest +import org.junit.jupiter.api.Test +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class WipingWipeRouteManagerTest2 : IntegrationTest() { + + @Test + fun `set up state is consistent`() = runBlocking { + assertFalse(lifecycleManager.hasBeenSetUp()) + addOwnerToken() + assertTrue(lifecycleManager.hasBeenSetUp()) + addContact(contact1) + addContact(contact2) + + // owner uploads a file + val uploadResponse: HttpResponse = httpClient.post("$baseUrl/files/${contact1.inboxId}") { + authenticateWithToken(ownerToken) + body = Random.nextBytes(42) + } + assertEquals(HttpStatusCode.OK, uploadResponse.status) + + // owner wipes mailbox + val response = httpClient.delete<HttpResponse>("$baseUrl/") { + authenticateWithToken(ownerToken) + } + assertEquals(HttpStatusCode.NoContent, response.status) + + // assert that database is gone + assertFalse(testComponent.getDatabaseConfig().getDatabaseDirectory().exists()) + + assertFalse(lifecycleManager.hasBeenSetUp()) + + // assert that database has been re-created by checking set-up state + assertTrue(testComponent.getDatabaseConfig().getDatabaseDirectory().exists()) + + // no more files are stored + val folderRoot = testComponent.getFileProvider().folderRoot + assertTrue(folderRoot.listFiles()?.isEmpty() ?: false) + + // no more contacts in DB - contacts table is gone + // it actually fails because db is closed though + assertFailsWith<DbException> { db.read { db.getContacts(it) } } + + // owner token was cleared as well - settings table is gone + // it actually fails because db is closed though + assertFailsWith<DbException> { + db.read { txn -> + testComponent.getSetupManager().getOwnerToken(txn) + } + } + + // re-open the database + db.open(null) + + // no more contacts in DB + assertTrue(db.read { db.getContacts(it) }.isEmpty()) + + // owner token was cleared as well + assertNull( + db.read { txn -> + testComponent.getSetupManager().getOwnerToken(txn) + } + ) + } + +}