diff --git a/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt b/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt index 7aca50684e5..80e6d223494 100644 --- a/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt +++ b/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt @@ -50,6 +50,7 @@ public class Auth( val request = HttpRequestBuilder() request.takeFromWithExecutionContext(context) + request.attributes.put(AuthHeaderAttribute, authHeader) provider.addRequestHeaders(request) request.attributes.put(circuitBreaker, Unit) @@ -67,3 +68,11 @@ public class Auth( public fun HttpClientConfig<*>.Auth(block: Auth.() -> Unit) { install(Auth, block) } + +/** + * AuthHeader from the previous unsuccessful request. This actually should be passed as + * parameter to AuthProvider.addRequestHeaders instead in the future and the attribute will + * be removed after that. + */ +@PublicAPICandidate("1.6.0") +internal val AuthHeaderAttribute = AttributeKey("AuthHeader") diff --git a/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/providers/DigestAuthProvider.kt b/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/providers/DigestAuthProvider.kt index f37506552e1..96de720b2e7 100644 --- a/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/providers/DigestAuthProvider.kt +++ b/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/providers/DigestAuthProvider.kt @@ -55,7 +55,6 @@ public class DigestAuthProvider( override fun isApplicable(auth: HttpAuthHeader): Boolean { if (auth !is HttpAuthHeader.Parameterized || - auth.parameter("realm") != realm || auth.authScheme != AuthScheme.Digest ) return false @@ -63,6 +62,11 @@ public class DigestAuthProvider( val newQop = auth.parameter("qop") val newOpaque = auth.parameter("opaque") + val newRealm = auth.parameter("realm") ?: return false + if (newRealm != realm && realm != null) { + return false + } + serverNonce.value = newNonce qop.value = newQop opaque.value = newOpaque @@ -90,6 +94,9 @@ public class DigestAuthProvider( } val token = makeDigest(tokenSequence.joinToString(":")) + val realm = realm ?: request.attributes.getOrNull(AuthHeaderAttribute)?.let { auth -> + (auth as? HttpAuthHeader.Parameterized)?.parameter("realm") + } val auth = HttpAuthHeader.Parameterized( AuthScheme.Digest, diff --git a/ktor-client/ktor-client-features/ktor-client-auth/common/test/io/ktor/client/features/auth/DigestProviderTest.kt b/ktor-client/ktor-client-features/ktor-client-auth/common/test/io/ktor/client/features/auth/DigestProviderTest.kt index 461278fb564..3c6e0cb77cf 100644 --- a/ktor-client/ktor-client-features/ktor-client-auth/common/test/io/ktor/client/features/auth/DigestProviderTest.kt +++ b/ktor-client/ktor-client-features/ktor-client-auth/common/test/io/ktor/client/features/auth/DigestProviderTest.kt @@ -56,6 +56,32 @@ class DigestProviderTest { checkStandardFields(authHeader) } + @Test + fun addRequestHeadersMissingRealm() = testSuspend { + if (!PlatformUtils.IS_JVM) return@testSuspend + + val providerWithoutRealm = DigestAuthProvider("username", "pass", null) + val authHeader = parseAuthorizationHeader(authAllFields)!! + requestBuilder.attributes.put(AuthHeaderAttribute, authHeader) + + assertTrue(providerWithoutRealm.isApplicable(authHeader)) + providerWithoutRealm.addRequestHeaders(requestBuilder) + + val resultAuthHeader = requestBuilder.headers[HttpHeaders.Authorization]!! + checkStandardFields(resultAuthHeader) + } + + @Test + fun addRequestHeadersChangedRealm() = testSuspend { + if (!PlatformUtils.IS_JVM) return@testSuspend + + val providerWithoutRealm = DigestAuthProvider("username", "pass", "wrong!") + val authHeader = parseAuthorizationHeader(authAllFields)!! + requestBuilder.attributes.put(AuthHeaderAttribute, authHeader) + + assertFalse(providerWithoutRealm.isApplicable(authHeader)) + } + @Test fun addRequestHeadersOmitsQopAndOpaqueWhenMissing() = testSuspend { if (!PlatformUtils.IS_JVM) return@testSuspend