Implement Rust-owned adaptive Android password navigation (#96)
This commit is contained in:
1 parent
36e5f55283
commit
8c9341f214
26 files changed
+1553
-16
No files matched your search
+128
@@ -1,6 +1,9 @@
|
||||
package de.rfc1437.ironstorage.bridge
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAuthenticationFfiException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileClock
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileEntryEditorInput
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingErrorKind
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingFfiException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
|
||||
@@ -18,6 +21,131 @@ import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AndroidOnboardingTest {
|
||||
@Test fun privateNavigationTraversesWithoutAuthenticationAndRejectsReplacedEntries() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val id = "navigation-${UUID.randomUUID()}"
|
||||
val directories = MobilePrivateDirectories(
|
||||
File(context.filesDir, id).apply { mkdirs() }.canonicalPath,
|
||||
File(context.noBackupFilesDir, id).apply { mkdirs() }.canonicalPath,
|
||||
File(context.cacheDir, id).apply { mkdirs() }.canonicalPath
|
||||
)
|
||||
val backend = MemoryBackend()
|
||||
val native = NativeRuntime(context)
|
||||
native.privateStorage(directories).use { runtime ->
|
||||
runtime.androidOnboarding(backend).use { setup ->
|
||||
val key =
|
||||
requireNotNull(
|
||||
setup.generate("Navigation fixture", "public-test-passphrase").selectedKey
|
||||
)
|
||||
setup.setupLocal(key, "public-test-passphrase")
|
||||
}
|
||||
val clock = object : MobileClock {
|
||||
override fun monotonicMilliseconds(): ULong =
|
||||
android.os.SystemClock.elapsedRealtime().toULong()
|
||||
}
|
||||
runtime.authentication(backend, clock).use { authentication ->
|
||||
authentication.createDirectory("", "仕事")
|
||||
authentication.createDirectory("仕事", "銀行")
|
||||
authentication.createDirectory("仕事/銀行", "Empty")
|
||||
val draft = authentication.beginCreateEntry(
|
||||
"仕事/銀行",
|
||||
"Long Unicode 密碼",
|
||||
"public-test-passphrase"
|
||||
)
|
||||
authentication.updateEntryEditor(
|
||||
draft.id,
|
||||
listOf(
|
||||
MobileEntryEditorInput(draft.page.fields[0].id, null, "fixture-password")
|
||||
)
|
||||
)
|
||||
authentication.saveEntryEditor(
|
||||
draft.id,
|
||||
listOf(
|
||||
MobileEntryEditorInput(draft.page.fields[0].id, null, "fixture-password")
|
||||
)
|
||||
)
|
||||
authentication.manualLock()
|
||||
val root = runtime.passwordNavigation("", null, null, false)
|
||||
val folder = runtime.passwordNavigation(
|
||||
"",
|
||||
root.rows[0].row.id,
|
||||
root.revision,
|
||||
true
|
||||
)
|
||||
assertEquals("仕事", folder.path)
|
||||
val bank = runtime.passwordNavigation("仕事/銀行", null, null, false)
|
||||
val row = bank.rows.single { it.row.title == "Long Unicode 密碼" }
|
||||
val selected = runtime.passwordNavigation(
|
||||
bank.path,
|
||||
row.row.id,
|
||||
bank.revision,
|
||||
true
|
||||
)
|
||||
assertEquals(row.row.id, selected.selected?.row?.id)
|
||||
assertFalse(authentication.state().unlocked)
|
||||
val presentation = authentication.navigationEntry(
|
||||
bank.path,
|
||||
row.row.id,
|
||||
bank.revision,
|
||||
"public-test-passphrase",
|
||||
0u
|
||||
)
|
||||
assertTrue(presentation.page.sections.isNotEmpty())
|
||||
assertTrue(
|
||||
presentation.page.sections.flatMap { it.fields }.none {
|
||||
it.value ==
|
||||
"fixture-password"
|
||||
}
|
||||
)
|
||||
authentication.manualLock()
|
||||
assertTrue(
|
||||
runtime.passwordNavigation("仕事/銀行/Empty", null, null, false).rows.isEmpty()
|
||||
)
|
||||
native.privateStorage(directories).use { recreated ->
|
||||
assertEquals(
|
||||
row.row.id,
|
||||
recreated.passwordNavigation(
|
||||
bank.path,
|
||||
row.row.id,
|
||||
bank.revision,
|
||||
false
|
||||
).selected?.row?.id
|
||||
)
|
||||
}
|
||||
authentication.unlockEntry(row.row.path, "public-test-passphrase")
|
||||
val editor = authentication.beginEntryEditor(row.row.path)
|
||||
authentication.updateEntryEditor(
|
||||
editor.id,
|
||||
listOf(
|
||||
MobileEntryEditorInput(editor.page.fields[0].id, null, "changed-password")
|
||||
)
|
||||
)
|
||||
authentication.saveEntryEditor(
|
||||
editor.id,
|
||||
listOf(
|
||||
MobileEntryEditorInput(editor.page.fields[0].id, null, "changed-password")
|
||||
)
|
||||
)
|
||||
authentication.manualLock()
|
||||
val stale = runtime.passwordNavigation(bank.path, row.row.id, bank.revision, true)
|
||||
assertTrue(stale.stale)
|
||||
assertEquals(null, stale.selected)
|
||||
assertThrows(
|
||||
MobileAuthenticationFfiException.Failed::class.java
|
||||
) {
|
||||
authentication.navigationEntry(
|
||||
bank.path,
|
||||
row.row.id,
|
||||
bank.revision,
|
||||
"public-test-passphrase",
|
||||
0u
|
||||
)
|
||||
}
|
||||
assertFalse(authentication.state().unlocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun realPublicHttpsDiscoveryAndConnectionUseThePlatformVerifier() {
|
||||
assumeTrue(
|
||||
InstrumentationRegistry.getArguments().getString("ironstorage_https_integration") ==
|
||||
|
||||
Reference in new issue
Block a user