Android: old host name out of the pinned-root config and the S-1 fixture
- Pinned-root network security config (D-082) is now a template in app/src/releasePinnedRoot/; with kvitt.tls.pinnedRoot=true the task generatePinnedRootNetworkSecurityConfig writes it into the release resources with the domain kvitt.publicHost (D-121), and stops with a clear message while the root src/release/res/raw/kvitt_root_ca.crt is not committed (fallback unprepared, SPEC 10.5.3). Comment: pinned NPM root (D-120). - Port 443 (D-124): RELEASE_SERVER_URL is https://<kvitt.publicHost> without a port (RELEASE_SERVER_PORT removed); the AC-2.6 intent filter has no android:port; InviteLink drops an explicit default port. - S-1 fixture GOLDEN_SERVER https://kvitt.example.org; 28 S-1 goldens re-recorded (only the server row changed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -35,7 +35,8 @@
|
||||
<data android:scheme="kvitt" android:host="invite" />
|
||||
</intent-filter>
|
||||
<!-- Unverified link: the user may get a chooser (AC-2.6). The host is the Gradle property kvitt.publicHost
|
||||
(BuildConfig.PUBLIC_HOST, D-121). -->
|
||||
(BuildConfig.PUBLIC_HOST, D-121); no port, since Kvitt is served on 443 (D-124). A filter without a
|
||||
port matches every port of the host. -->
|
||||
<intent-filter android:autoVerify="false">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -43,7 +44,6 @@
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="${publicHost}"
|
||||
android:port="8443"
|
||||
android:pathPrefix="/i/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@@ -26,8 +26,8 @@ sealed interface AppLink {
|
||||
|
||||
/**
|
||||
* Parses the intents `MainActivity` receives: `kvitt://invite?server=…&code=…[&from=…]` and
|
||||
* `https://<BuildConfig.PUBLIC_HOST>:8443/i/<CODE>[?from=…]` (AC-2.6; the manifest's intent filter takes its host from the
|
||||
* `publicHost` placeholder, D-121), both through `:feature:onboarding`'s [InviteLink], the reader S-1 uses for pasted
|
||||
* `https://<BuildConfig.PUBLIC_HOST>/i/<CODE>[?from=…]` (AC-2.6, D-124; the manifest's intent filter takes its host from
|
||||
* the `publicHost` placeholder, D-121), both through `:feature:onboarding`'s [InviteLink], the reader S-1 uses for pasted
|
||||
* links, which takes the server from the link itself, and the notification taps this app builds ([openIntent]).
|
||||
*/
|
||||
object AppLinks {
|
||||
|
||||
@@ -4,8 +4,9 @@ import se.hagstedt.kvitt.BuildConfig
|
||||
import se.hagstedt.kvitt.feature.onboarding.releaseServerUrl
|
||||
|
||||
/**
|
||||
* The release server, `https://<BuildConfig.PUBLIC_HOST>:8443` (AC-2.7, D-121): prefilled on S-1 and the host of the
|
||||
* `https` invite links the manifest declares (AC-2.6, the `publicHost` placeholder). `PUBLIC_HOST` is the Gradle
|
||||
* property `kvitt.publicHost`; without it the build uses the placeholder `kvitt.example.invalid`, which never resolves.
|
||||
* The release server, `https://<BuildConfig.PUBLIC_HOST>` on port 443 (AC-2.7, D-121, D-124): prefilled on S-1 and the
|
||||
* host of the `https` invite links the manifest declares (AC-2.6, the `publicHost` placeholder). `PUBLIC_HOST` is the
|
||||
* Gradle property `kvitt.publicHost`; without it the build uses the placeholder `kvitt.example.invalid`, which never
|
||||
* resolves.
|
||||
*/
|
||||
val RELEASE_SERVER_URL: String = releaseServerUrl(BuildConfig.PUBLIC_HOST)
|
||||
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPEC §10.5.3 fallback, used only with kvitt.tls.pinnedRoot=true: the Caddy internal root (committed as
|
||||
src/release/res/raw/kvitt_root_ca.crt) replaces the system anchors for icehack.asuscomm.com only. -->
|
||||
<!-- SPEC §10.5.3 fallback, used only with kvitt.tls.pinnedRoot=true (D-082): a self-made root behind NPM (D-120),
|
||||
committed as src/release/res/raw/kvitt_root_ca.crt when the fallback is prepared, replaces the system anchors for
|
||||
Kvitt's public host only. Template: the build writes it into the release resources with @PUBLIC_HOST@ replaced
|
||||
by the Gradle property kvitt.publicHost (D-121); see generatePinnedRootNetworkSecurityConfig in build.gradle.kts. -->
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
@@ -8,7 +10,7 @@
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="false">icehack.asuscomm.com</domain>
|
||||
<domain includeSubdomains="false">@PUBLIC_HOST@</domain>
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/kvitt_root_ca" />
|
||||
</trust-anchors>
|
||||
@@ -23,10 +23,11 @@ class AppLinksTest {
|
||||
private val host = BuildConfig.PUBLIC_HOST
|
||||
|
||||
@Test
|
||||
fun theReleaseServerIsHttpsOnPort8443OfThePublicHost() {
|
||||
assertEquals("https://$host:8443", RELEASE_SERVER_URL)
|
||||
fun theReleaseServerIsHttpsOnTheDefaultPortOfThePublicHost() {
|
||||
// D-124: port 443, written without a port.
|
||||
assertEquals("https://$host", RELEASE_SERVER_URL)
|
||||
// Without kvitt.publicHost the build uses the placeholder, which never resolves (RFC 2606).
|
||||
assertEquals("https://kvitt.example.invalid:8443", releaseServerUrl("kvitt.example.invalid"))
|
||||
assertEquals("https://kvitt.example.invalid", releaseServerUrl("kvitt.example.invalid"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,8 +38,13 @@ class AppLinksTest {
|
||||
0,
|
||||
).any { it.activityInfo.name == "se.hagstedt.kvitt.MainActivity" }
|
||||
assertTrue(opens("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN"))
|
||||
assertFalse(opens("https://other.example.org:8443/i/ABCDEFGHJKMN"))
|
||||
assertFalse(opens("https://$host/i/ABCDEFGHJKMN"))
|
||||
assertTrue(opens("https://$host/i/ABCDEFGHJKMN?from=Jacob"))
|
||||
assertFalse(opens("https://other.example.org/i/ABCDEFGHJKMN"))
|
||||
assertFalse(opens("http://$host/i/ABCDEFGHJKMN"))
|
||||
assertFalse(opens("https://$host/apk/latest"))
|
||||
// A filter without a port matches every port of the host (android:port="443" would match no link without an
|
||||
// explicit port), so an old :8443 link still opens S-1, with the server the link names (AC-2.6).
|
||||
assertTrue(opens("https://$host:8443/i/ABCDEFGHJKMN"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,13 +56,15 @@ class AppLinksTest {
|
||||
@Test
|
||||
fun appLinkNormalisesTheCode() {
|
||||
// §8.3: lowercase, O -> 0, I and L -> 1.
|
||||
assertEquals("ABCD0113EFGH", AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F$host%3A8443&code=abcdo1l3efgh")?.code)
|
||||
assertEquals("ABCD0113EFGH", AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F$host&code=abcdo1l3efgh")?.code)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun landingLinkFillsTheServerFromTheLink() {
|
||||
val link = AppLinks.parseUri("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN")
|
||||
assertEquals(AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN"), link)
|
||||
// An explicit default port names the same server.
|
||||
assertEquals(AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN"), AppLinks.parseUri("https://$host:443/i/ABCDEFGHJKMN"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -67,7 +75,7 @@ class AppLinksTest {
|
||||
)
|
||||
assertEquals(
|
||||
AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN", "Jacob"),
|
||||
AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F${host.replace(".", "%2E")}%3A8443&code=ABCDEFGHJKMN&from=Jacob"),
|
||||
AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F${host.replace(".", "%2E")}&code=ABCDEFGHJKMN&from=Jacob"),
|
||||
)
|
||||
assertNull(AppLinks.parseUri("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN")?.adminName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user