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 007ae9a7881cbd8119c3f07cdfc91f8e076e94da..1157de2d83417d1d2814c0fbec755814d43b854c 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 771167d3aa077a542167416c5dafd3cf7a329313..96889cbbb6a9871806910feeffa4602beb9cdf6e 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 0000000000000000000000000000000000000000..3da9b2e0cb39188ef58e20f1e2a6996db7210b3d --- /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 564531b71e307b705f0864f5c9559cc752fc057a..6eeff9ca8f6c74f13c17479c5f3e0fc58c656c81 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 0000000000000000000000000000000000000000..23fb16a71573106beaa0761d53702ba86e1d6ffc --- /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) + } + +}