sync: refuse Basic over cleartext even when challenged
insecurePreemptive gated only the preemptive branch, so a plain-HTTP server answering 401 with a Basic challenge still got the password in a header every hop can read. The flag's name was accurate; its coverage was not. The gate moves onto the Basic emission, covering both paths, and the flag becomes insecureBasic. Digest is left answered over cleartext: it never puts the password on the wire, and refusing it would break a LAN server the day the per-account cleartext opt-in ships. Not reachable in the app today -- network_security_config forbids cleartext outright and nothing passes allowCleartext -- but :caldav is a plain JVM module meant for reuse, where neither of those holds. Upstream's testBasic asserted this exact behaviour over http://, so it is amended to https:// and the cleartext cases are pinned explicitly. PROVENANCE change 9 records it as the one upstream test this port changes rather than inherits.
This commit is contained in:
@@ -18,11 +18,15 @@ import org.junit.Test
|
||||
|
||||
class BasicDigestAuthHandlerTest {
|
||||
|
||||
// ⚠️ Amended from upstream: https, not http. Basic over cleartext is now
|
||||
// refused whether or not it was challenged — see PROVENANCE change 9. The
|
||||
// cleartext behaviour this used to assert is pinned by
|
||||
// `cleartextBasicIsRefusedEvenWhenChallenged` below.
|
||||
@Test
|
||||
fun testBasic() {
|
||||
var authenticator = BasicDigestAuthHandler(null, "user", "password")
|
||||
val original = Request.Builder()
|
||||
.url("http://example.com")
|
||||
.url("https://example.com")
|
||||
.build()
|
||||
var response = Builder()
|
||||
.request(original)
|
||||
@@ -42,6 +46,55 @@ class BasicDigestAuthHandlerTest {
|
||||
assertEquals("Basic dXNlcm5hbWU6cGHDn3dvcmQ=", request!!.header("Authorization"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cleartextBasicIsRefusedEvenWhenChallenged() {
|
||||
val authenticator = BasicDigestAuthHandler(null, "user", "password")
|
||||
val original = Request.Builder().url("http://example.com").build()
|
||||
val response = Builder()
|
||||
.request(original)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(401).message("Authentication required")
|
||||
.header("WWW-Authenticate", "Basic realm=\"WallyWorld\"")
|
||||
.build()
|
||||
|
||||
assertNull(authenticator.authenticateRequest(original, response))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cleartextBasicIsSentWhenExplicitlyAllowed() {
|
||||
val authenticator = BasicDigestAuthHandler(null, "user", "password", insecureBasic = true)
|
||||
val original = Request.Builder().url("http://example.com").build()
|
||||
val response = Builder()
|
||||
.request(original)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(401).message("Authentication required")
|
||||
.header("WWW-Authenticate", "Basic realm=\"WallyWorld\"")
|
||||
.build()
|
||||
|
||||
val request = authenticator.authenticateRequest(original, response)
|
||||
assertEquals("Basic dXNlcjpwYXNzd29yZA==", request!!.header("Authorization"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cleartextDigestIsStillAnswered() {
|
||||
// Digest never puts the password on the wire, so gating it would break a
|
||||
// LAN server for nothing.
|
||||
val authenticator = BasicDigestAuthHandler(null, "user", "password")
|
||||
val original = Request.Builder().url("http://example.com").build()
|
||||
val response = Builder()
|
||||
.request(original)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(401).message("Authentication required")
|
||||
.header(
|
||||
"WWW-Authenticate",
|
||||
"Digest realm=\"WallyWorld\", nonce=\"abc\", qop=\"auth\"",
|
||||
)
|
||||
.build()
|
||||
|
||||
val request = authenticator.authenticateRequest(original, response)
|
||||
assertTrue(request!!.header("Authorization")!!.startsWith("Digest"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDigestRFCExample() {
|
||||
// use cnonce from example
|
||||
|
||||
Reference in New Issue
Block a user