SHA256
Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
67 lines
2.6 KiB
Kotlin
67 lines
2.6 KiB
Kotlin
package community.yuvomi.mobile
|
|
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.webkit.WebResourceRequest
|
|
import android.webkit.WebView
|
|
import androidx.test.core.app.ActivityScenario
|
|
import androidx.test.core.app.ApplicationProvider
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import org.junit.Assert.assertEquals
|
|
import org.junit.Assert.assertNotNull
|
|
import org.junit.Assert.assertNull
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
class BrowserSecurityTest {
|
|
@Test fun blocksCrossOriginPostAndSubresourceBeforeNetwork() {
|
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
context.getSharedPreferences("server", Context.MODE_PRIVATE).edit()
|
|
.putString("origin", "https://family.example").commit()
|
|
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
|
|
lateinit var web: WebView
|
|
lateinit var client: android.webkit.WebViewClient
|
|
scenario.onActivity {
|
|
web = requireNotNull(findWeb(it.window.decorView))
|
|
client = web.webViewClient
|
|
}
|
|
// Interception runs off the UI thread in WebView. POST navigations do
|
|
// not reliably call shouldOverrideUrlLoading, so this is a separate gate.
|
|
for (mainFrame in listOf(true, false)) {
|
|
val response = client.shouldInterceptRequest(
|
|
web,
|
|
Request("https://evil.example/collect", mainFrame),
|
|
)
|
|
assertNotNull(response)
|
|
assertEquals(403, response!!.statusCode)
|
|
}
|
|
assertNull(
|
|
client.shouldInterceptRequest(web, Request("https://family.example/tasks", true)),
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun findWeb(view: View): WebView? {
|
|
if (view is WebView) return view
|
|
if (view is ViewGroup) {
|
|
for (i in 0 until view.childCount) {
|
|
findWeb(view.getChildAt(i))?.let {
|
|
return it
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
private class Request(private val value: String, private val mainFrame: Boolean) : WebResourceRequest {
|
|
override fun getUrl(): Uri = Uri.parse(value)
|
|
override fun isForMainFrame() = mainFrame
|
|
override fun isRedirect() = false
|
|
override fun hasGesture() = true
|
|
override fun getMethod() = "POST"
|
|
override fun getRequestHeaders(): MutableMap<String, String> = mutableMapOf()
|
|
}
|
|
}
|