Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions auth0/src/main/java/com/auth0/android/request/DefaultClient.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.auth0.android.request

import androidx.annotation.VisibleForTesting
import com.auth0.android.dpop.DPoPUtil
import com.auth0.android.request.internal.GsonProvider
import com.google.gson.Gson
import okhttp3.*
import okhttp3.Call
import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
Expand Down Expand Up @@ -56,6 +62,12 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val okHttpClient: OkHttpClient

// Using another client to prevent OkHttp from retrying network calls especially when using DPoP with replay protection mechanism.
// https://auth0team.atlassian.net/browse/ESD-56048.
// TODO: This should be replaced with the chain.retryOnConnectionFailure() API when we update to OkHttp 5+
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val nonRetryableOkHttpClient: OkHttpClient

@Throws(IllegalArgumentException::class, IOException::class)
override fun load(url: String, options: RequestOptions): ServerResponse {
val response = prepareCall(url.toHttpUrl(), options).execute()
Expand Down Expand Up @@ -90,12 +102,31 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
.url(urlBuilder.build())
.headers(headers)
.build()
return okHttpClient.newCall(request)

// Use non-retryable client for DPoP requests
val client = if (shouldUseNonRetryableClient(headers)) {
nonRetryableOkHttpClient
} else {
okHttpClient
}

return client.newCall(request)
}

/**
* Determines if the request should use the non-retryable OkHttpClient.
* Returns true for:
* 1. Requests with DPoP header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have other conditions too for which non retryable client must be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently Dpop scenario is where this is causing major error. We can update these cases on need to basis like refresh token exchange etc

*/
private fun shouldUseNonRetryableClient(
headers: Headers
): Boolean {
return headers[DPoPUtil.DPOP_HEADER] != null
}

init {
// client setup
val builder = OkHttpClient.Builder()
// Add retry interceptor
builder.addInterceptor(RetryInterceptor())

// logging
Expand All @@ -115,6 +146,11 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
}

okHttpClient = builder.build()

// Non-retryable client for DPoP requests
nonRetryableOkHttpClient = okHttpClient.newBuilder()
.retryOnConnectionFailure(false)
.build()
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,43 @@ public class DefaultClientTest {
requestAssertions(sentRequest, HttpMethod.PATCH)
}

@Test
public fun shouldHaveNonRetryableClientConfigured() {
val client = createDefaultClientForTest(mapOf())

assertThat(client.okHttpClient, notNullValue())
assertThat(client.nonRetryableOkHttpClient, notNullValue())

assertThat(client.okHttpClient.retryOnConnectionFailure, equalTo(true))
assertThat(client.nonRetryableOkHttpClient.retryOnConnectionFailure, equalTo(false))
}

@Test
public fun shouldShareSameConfigBetweenClients() {
val client = createDefaultClientForTest(mapOf())

assertThat(
client.okHttpClient.interceptors.size,
equalTo(client.nonRetryableOkHttpClient.interceptors.size)
)

assertThat(
client.okHttpClient.interceptors[0] is RetryInterceptor,
equalTo(true)
)
assertThat(
client.nonRetryableOkHttpClient.interceptors[0] is RetryInterceptor,
equalTo(true)
)
assertThat(
client.okHttpClient.connectTimeoutMillis,
equalTo(client.nonRetryableOkHttpClient.connectTimeoutMillis)
)
assertThat(
client.okHttpClient.readTimeoutMillis,
equalTo(client.nonRetryableOkHttpClient.readTimeoutMillis)
)
}

//Helper methods
private fun requestAssertions(
Expand Down