mirror of
https://github.com/square/okhttp.git
synced 2025-07-31 05:04:26 +03:00
Hide the value of sensitive query parameters in log (#8242)
Add option to redact sensitive query params.
This commit is contained in:
@ -8,6 +8,7 @@ public final class okhttp3/logging/HttpLoggingInterceptor : okhttp3/Interceptor
|
|||||||
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
|
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
|
||||||
public final fun level (Lokhttp3/logging/HttpLoggingInterceptor$Level;)V
|
public final fun level (Lokhttp3/logging/HttpLoggingInterceptor$Level;)V
|
||||||
public final fun redactHeader (Ljava/lang/String;)V
|
public final fun redactHeader (Ljava/lang/String;)V
|
||||||
|
public final fun redactQueryParams ([Ljava/lang/String;)V
|
||||||
public final fun setLevel (Lokhttp3/logging/HttpLoggingInterceptor$Level;)Lokhttp3/logging/HttpLoggingInterceptor;
|
public final fun setLevel (Lokhttp3/logging/HttpLoggingInterceptor$Level;)Lokhttp3/logging/HttpLoggingInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import java.nio.charset.Charset
|
|||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -46,6 +47,8 @@ class HttpLoggingInterceptor
|
|||||||
) : Interceptor {
|
) : Interceptor {
|
||||||
@Volatile private var headersToRedact = emptySet<String>()
|
@Volatile private var headersToRedact = emptySet<String>()
|
||||||
|
|
||||||
|
@Volatile private var queryParamsNameToRedact = emptySet<String>()
|
||||||
|
|
||||||
@set:JvmName("level")
|
@set:JvmName("level")
|
||||||
@Volatile
|
@Volatile
|
||||||
var level = Level.NONE
|
var level = Level.NONE
|
||||||
@ -132,6 +135,13 @@ class HttpLoggingInterceptor
|
|||||||
headersToRedact = newHeadersToRedact
|
headersToRedact = newHeadersToRedact
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun redactQueryParams(vararg name: String) {
|
||||||
|
val newQueryParamsNameToRedact = TreeSet(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
newQueryParamsNameToRedact += queryParamsNameToRedact
|
||||||
|
newQueryParamsNameToRedact.addAll(name)
|
||||||
|
queryParamsNameToRedact = newQueryParamsNameToRedact
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the level and returns this.
|
* Sets the level and returns this.
|
||||||
*
|
*
|
||||||
@ -168,7 +178,7 @@ class HttpLoggingInterceptor
|
|||||||
|
|
||||||
val connection = chain.connection()
|
val connection = chain.connection()
|
||||||
var requestStartMessage =
|
var requestStartMessage =
|
||||||
("--> ${request.method} ${request.url}${if (connection != null) " " + connection.protocol() else ""}")
|
("--> ${request.method} ${redactUrl(request.url)}${if (connection != null) " " + connection.protocol() else ""}")
|
||||||
if (!logHeaders && requestBody != null) {
|
if (!logHeaders && requestBody != null) {
|
||||||
requestStartMessage += " (${requestBody.contentLength()}-byte body)"
|
requestStartMessage += " (${requestBody.contentLength()}-byte body)"
|
||||||
}
|
}
|
||||||
@ -251,7 +261,7 @@ class HttpLoggingInterceptor
|
|||||||
buildString {
|
buildString {
|
||||||
append("<-- ${response.code}")
|
append("<-- ${response.code}")
|
||||||
if (response.message.isNotEmpty()) append(" ${response.message}")
|
if (response.message.isNotEmpty()) append(" ${response.message}")
|
||||||
append(" ${response.request.url} (${tookMs}ms")
|
append(" ${redactUrl(response.request.url)} (${tookMs}ms")
|
||||||
if (!logHeaders) append(", $bodySize body")
|
if (!logHeaders) append(", $bodySize body")
|
||||||
append(")")
|
append(")")
|
||||||
},
|
},
|
||||||
@ -312,6 +322,20 @@ class HttpLoggingInterceptor
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun redactUrl(url: HttpUrl): String {
|
||||||
|
if (queryParamsNameToRedact.isEmpty() || url.querySize == 0) {
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
return url.newBuilder().query(null).apply {
|
||||||
|
for (i in 0 until url.querySize) {
|
||||||
|
val parameterName = url.queryParameterName(i)
|
||||||
|
val newValue = if (parameterName in queryParamsNameToRedact) "██" else url.queryParameterValue(i)
|
||||||
|
|
||||||
|
addEncodedQueryParameter(parameterName, newValue)
|
||||||
|
}
|
||||||
|
}.toString()
|
||||||
|
}
|
||||||
|
|
||||||
private fun logHeader(
|
private fun logHeader(
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
i: Int,
|
i: Int,
|
||||||
|
@ -903,6 +903,104 @@ class HttpLoggingInterceptorTest {
|
|||||||
.assertNoMoreLogs()
|
.assertNoMoreLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
@Test
|
||||||
|
fun sensitiveQueryParamsAreRedacted() {
|
||||||
|
url = server.url("/api/login?user=test_user&authentication=basic&password=confidential_password")
|
||||||
|
val networkInterceptor =
|
||||||
|
HttpLoggingInterceptor(networkLogs).setLevel(
|
||||||
|
Level.BASIC,
|
||||||
|
)
|
||||||
|
networkInterceptor.redactQueryParams("user", "passWord")
|
||||||
|
|
||||||
|
val applicationInterceptor =
|
||||||
|
HttpLoggingInterceptor(applicationLogs).setLevel(
|
||||||
|
Level.BASIC,
|
||||||
|
)
|
||||||
|
applicationInterceptor.redactQueryParams("user", "PassworD")
|
||||||
|
|
||||||
|
client =
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(networkInterceptor)
|
||||||
|
.addInterceptor(applicationInterceptor)
|
||||||
|
.build()
|
||||||
|
server.enqueue(
|
||||||
|
MockResponse.Builder()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
val response =
|
||||||
|
client
|
||||||
|
.newCall(
|
||||||
|
request()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
response.body.close()
|
||||||
|
val redactedUrl = networkInterceptor.redactUrl(url)
|
||||||
|
val redactedUrlPattern = redactedUrl.replace("?", """\?""")
|
||||||
|
applicationLogs
|
||||||
|
.assertLogEqual("--> GET $redactedUrl")
|
||||||
|
.assertLogMatch(Regex("""<-- 200 OK $redactedUrlPattern \(\d+ms, \d+-byte body\)"""))
|
||||||
|
.assertNoMoreLogs()
|
||||||
|
networkLogs
|
||||||
|
.assertLogEqual("--> GET $redactedUrl http/1.1")
|
||||||
|
.assertLogMatch(Regex("""<-- 200 OK $redactedUrlPattern \(\d+ms, \d+-byte body\)"""))
|
||||||
|
.assertNoMoreLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
@Test
|
||||||
|
fun preserveQueryParamsAfterRedacted() {
|
||||||
|
url =
|
||||||
|
server.url(
|
||||||
|
"""/api/login?
|
||||||
|
|user=test_user&
|
||||||
|
|authentication=basic&
|
||||||
|
|password=confidential_password&
|
||||||
|
|authentication=rather simple login method
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
val networkInterceptor =
|
||||||
|
HttpLoggingInterceptor(networkLogs).setLevel(
|
||||||
|
Level.BASIC,
|
||||||
|
)
|
||||||
|
networkInterceptor.redactQueryParams("user", "passWord")
|
||||||
|
|
||||||
|
val applicationInterceptor =
|
||||||
|
HttpLoggingInterceptor(applicationLogs).setLevel(
|
||||||
|
Level.BASIC,
|
||||||
|
)
|
||||||
|
applicationInterceptor.redactQueryParams("user", "PassworD")
|
||||||
|
|
||||||
|
client =
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(networkInterceptor)
|
||||||
|
.addInterceptor(applicationInterceptor)
|
||||||
|
.build()
|
||||||
|
server.enqueue(
|
||||||
|
MockResponse.Builder()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
val response =
|
||||||
|
client
|
||||||
|
.newCall(
|
||||||
|
request()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
response.body.close()
|
||||||
|
val redactedUrl = networkInterceptor.redactUrl(url)
|
||||||
|
val redactedUrlPattern = redactedUrl.replace("?", """\?""")
|
||||||
|
applicationLogs
|
||||||
|
.assertLogEqual("--> GET $redactedUrl")
|
||||||
|
.assertLogMatch(Regex("""<-- 200 OK $redactedUrlPattern \(\d+ms, \d+-byte body\)"""))
|
||||||
|
.assertNoMoreLogs()
|
||||||
|
networkLogs
|
||||||
|
.assertLogEqual("--> GET $redactedUrl http/1.1")
|
||||||
|
.assertLogMatch(Regex("""<-- 200 OK $redactedUrlPattern \(\d+ms, \d+-byte body\)"""))
|
||||||
|
.assertNoMoreLogs()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun duplexRequestsAreNotLogged() {
|
fun duplexRequestsAreNotLogged() {
|
||||||
platform.assumeHttp2Support()
|
platform.assumeHttp2Support()
|
||||||
|
Reference in New Issue
Block a user