From 8743ef5440d7fe57ffb15b522b8504ff97bf9207 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Mon, 25 Oct 2021 10:17:52 -0300 Subject: [PATCH] API endpoint for wiping the mailbox --- .../mailbox/core/server/Routing.kt | 11 ++- .../mailbox/core/server/WebServerManager.kt | 4 +- .../mailbox/core/setup/WipeManager.kt | 37 ++++++++++ .../mailbox/core/TestComponent.kt | 2 + .../mailbox/core/setup/WipeManagerTest.kt | 71 +++++++++++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt create mode 100644 mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeManagerTest.kt diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/Routing.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/Routing.kt index 007ae9a7..1157de2d 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/Routing.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/Routing.kt @@ -7,7 +7,6 @@ import io.ktor.auth.authenticate import io.ktor.features.BadRequestException import io.ktor.features.MissingRequestParameterException import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode.Companion.BadRequest import io.ktor.http.HttpStatusCode.Companion.Unauthorized import io.ktor.response.respond @@ -22,18 +21,24 @@ import io.ktor.util.getOrFail import org.briarproject.mailbox.core.contacts.ContactsManager import org.briarproject.mailbox.core.files.FileManager import org.briarproject.mailbox.core.setup.SetupManager +import org.briarproject.mailbox.core.setup.WipeManager import org.briarproject.mailbox.core.system.InvalidIdException internal const val V = "/" // TODO set to "/v1" for release -internal fun Application.configureBasicApi(setupManager: SetupManager) = routing { +internal fun Application.configureBasicApi( + setupManager: SetupManager, + wipeManager: WipeManager, +) = routing { route(V) { get { call.respondText("Hello world!", ContentType.Text.Plain) } authenticate { delete { - call.respond(HttpStatusCode.OK, "delete: Not yet implemented") + call.handle { + wipeManager.onWipeRequest(call) + } } put("/setup") { call.handle { diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/WebServerManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/WebServerManager.kt index 771167d3..96889cbb 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/WebServerManager.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/WebServerManager.kt @@ -12,6 +12,7 @@ import org.briarproject.mailbox.core.files.FileManager import org.briarproject.mailbox.core.lifecycle.Service import org.briarproject.mailbox.core.server.WebServerManager.Companion.PORT import org.briarproject.mailbox.core.setup.SetupManager +import org.briarproject.mailbox.core.setup.WipeManager import org.slf4j.Logger import org.slf4j.LoggerFactory.getLogger import javax.inject.Inject @@ -29,6 +30,7 @@ internal class WebServerManagerImpl @Inject constructor( private val setupManager: SetupManager, private val contactsManager: ContactsManager, private val fileManager: FileManager, + private val wipeManager: WipeManager, ) : WebServerManager { internal companion object { @@ -50,7 +52,7 @@ internal class WebServerManagerImpl @Inject constructor( install(ContentNegotiation) { jackson() } - configureBasicApi(setupManager) + configureBasicApi(setupManager, wipeManager) configureContactApi(contactsManager) configureFilesApi(fileManager) } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt new file mode 100644 index 00000000..3da9b2e0 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt @@ -0,0 +1,37 @@ +package org.briarproject.mailbox.core.setup + +import io.ktor.application.ApplicationCall +import io.ktor.auth.principal +import io.ktor.http.HttpStatusCode +import io.ktor.response.respond +import org.briarproject.mailbox.core.db.Database +import org.briarproject.mailbox.core.files.FileManager +import org.briarproject.mailbox.core.server.AuthException +import org.briarproject.mailbox.core.server.MailboxPrincipal +import javax.inject.Inject + +class WipeManager @Inject constructor( + private val db: Database, + private val fileManager: FileManager, +) { + + /** + * Handler for `DELETE /` API endpoint. + * + * Wipes entire database as well as stored files + * and returns `204 No Content` response if successful + */ + @Throws(AuthException::class) + suspend fun onWipeRequest(call: ApplicationCall) { + val principal = call.principal<MailboxPrincipal>() + if (principal !is MailboxPrincipal.OwnerPrincipal) throw AuthException() + + db.transaction(false) { txn -> + db.clearDatabase(txn) + } + fileManager.deleteAllFiles() + + call.respond(HttpStatusCode.NoContent) + } + +} diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt index 564531b7..6eeff9ca 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt @@ -3,6 +3,7 @@ package org.briarproject.mailbox.core import dagger.Component import org.briarproject.mailbox.core.db.Database import org.briarproject.mailbox.core.files.FileManager +import org.briarproject.mailbox.core.files.FileProvider import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.settings.SettingsManager import org.briarproject.mailbox.core.setup.SetupManager @@ -23,4 +24,5 @@ interface TestComponent { fun getFileManager(): FileManager fun getDatabase(): Database fun getRandomIdManager(): RandomIdManager + fun getFileProvider(): FileProvider } diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeManagerTest.kt new file mode 100644 index 00000000..23fb16a7 --- /dev/null +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeManagerTest.kt @@ -0,0 +1,71 @@ +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.server.IntegrationTest +import org.junit.jupiter.api.Test +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class WipeManagerTest : IntegrationTest() { + + private val db by lazy { testComponent.getDatabase() } + + @Test + fun `wipe request rejects non-owners`() = runBlocking { + addOwnerToken() + addContact(contact1) + + // 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) + } + + @Test + fun `wipe request deletes files and db for owner`() = runBlocking { + addOwnerToken() + 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) + + // no more contacts in DB + val contacts = db.transactionWithResult(true) { db.getContacts(it) } + assertEquals(0, contacts.size) + + // owner token was cleared as well + val token = db.transactionWithResult(true) { txn -> + testComponent.getSetupManager().getOwnerToken(txn) + } + assertNull(token) + + // no more files are stored + val folderRoot = testComponent.getFileProvider().folderRoot + assertTrue(folderRoot.listFiles()?.isEmpty() ?: false) + } + +} -- GitLab