Skip to content

Commit 2b3a4ff

Browse files
committedFeb 6, 2024
add option to redact sensitive query params
1 parent 02df2bb commit 2b3a4ff

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed
 

‎okhttp-logging-interceptor/api/logging-interceptor.api

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public final class okhttp3/logging/HttpLoggingInterceptor : okhttp3/Interceptor
77
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
88
public final fun level (Lokhttp3/logging/HttpLoggingInterceptor$Level;)V
99
public final fun redactHeader (Ljava/lang/String;)V
10+
public final fun redactQueryParams ([Ljava/lang/String;)V
1011
public final fun setLevel (Lokhttp3/logging/HttpLoggingInterceptor$Level;)Lokhttp3/logging/HttpLoggingInterceptor;
1112
}
1213

‎okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt

+35-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.nio.charset.Charset
2222
import java.util.TreeSet
2323
import java.util.concurrent.TimeUnit
2424
import okhttp3.Headers
25+
import okhttp3.HttpUrl
2526
import okhttp3.Interceptor
2627
import okhttp3.OkHttpClient
2728
import okhttp3.Response
@@ -46,6 +47,8 @@ class HttpLoggingInterceptor
4647
) : Interceptor {
4748
@Volatile private var headersToRedact = emptySet<String>()
4849

50+
@Volatile private var queryParamsNameToRedact = emptySet<String>()
51+
4952
@set:JvmName("level")
5053
@Volatile
5154
var level = Level.NONE
@@ -132,6 +135,13 @@ class HttpLoggingInterceptor
132135
headersToRedact = newHeadersToRedact
133136
}
134137

138+
fun redactQueryParams(vararg name: String) {
139+
val newQueryParamsNameToRedact = TreeSet(String.CASE_INSENSITIVE_ORDER)
140+
newQueryParamsNameToRedact += queryParamsNameToRedact
141+
newQueryParamsNameToRedact.addAll(name)
142+
queryParamsNameToRedact = newQueryParamsNameToRedact
143+
}
144+
135145
/**
136146
* Sets the level and returns this.
137147
*
@@ -168,7 +178,7 @@ class HttpLoggingInterceptor
168178

169179
val connection = chain.connection()
170180
var requestStartMessage =
171-
("--> ${request.method} ${request.url}${if (connection != null) " " + connection.protocol() else ""}")
181+
("--> ${request.method} ${sanitizeUrl(request.url)}${if (connection != null) " " + connection.protocol() else ""}")
172182
if (!logHeaders && requestBody != null) {
173183
requestStartMessage += " (${requestBody.contentLength()}-byte body)"
174184
}
@@ -251,7 +261,7 @@ class HttpLoggingInterceptor
251261
buildString {
252262
append("<-- ${response.code}")
253263
if (response.message.isNotEmpty()) append(" ${response.message}")
254-
append(" ${response.request.url} (${tookMs}ms")
264+
append(" ${sanitizeUrl(response.request.url)} (${tookMs}ms")
255265
if (!logHeaders) append(", $bodySize body")
256266
append(")")
257267
},
@@ -312,6 +322,29 @@ class HttpLoggingInterceptor
312322
return response
313323
}
314324

325+
private fun sanitizeUrl(url: HttpUrl): String {
326+
val params = ArrayList<Pair<String, String>>()
327+
for (parameterName in url.queryParameterNames) {
328+
params.add(
329+
Pair(
330+
first = parameterName,
331+
second = if (parameterName in queryParamsNameToRedact) "██" else url.queryParameter(parameterName) ?: "",
332+
),
333+
)
334+
}
335+
val queryParamsBuilder = StringBuilder()
336+
val totalParam = params.size
337+
if (totalParam > 0) {
338+
queryParamsBuilder.append("?")
339+
params.forEachIndexed { index, param ->
340+
val suffix = if (index != totalParam - 1) "&" else ""
341+
queryParamsBuilder.append("${param.first}=${param.second}$suffix")
342+
}
343+
}
344+
345+
return "${url.toString().substringBefore("?")}$queryParamsBuilder"
346+
}
347+
315348
private fun logHeader(
316349
headers: Headers,
317350
i: Int,

‎okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.kt

+45
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,51 @@ class HttpLoggingInterceptorTest {
903903
.assertNoMoreLogs()
904904
}
905905

906+
@Test
907+
fun sensitiveQueryParamsAreRedacted() {
908+
url = server.url("/api/login?user=test_user&authentication=basic&password=confidential_password")
909+
val sanitizedUrl = "${server.url("/")}api/login?user=██&authentication=basic&password=██"
910+
val sanitizedUrlPattern = sanitizedUrl.replace("?", """\?""")
911+
912+
val networkInterceptor =
913+
HttpLoggingInterceptor(networkLogs).setLevel(
914+
Level.BASIC,
915+
)
916+
networkInterceptor.redactQueryParams("user", "passWord")
917+
918+
val applicationInterceptor =
919+
HttpLoggingInterceptor(applicationLogs).setLevel(
920+
Level.BASIC,
921+
)
922+
applicationInterceptor.redactQueryParams("user", "PassworD")
923+
924+
client =
925+
OkHttpClient.Builder()
926+
.addNetworkInterceptor(networkInterceptor)
927+
.addInterceptor(applicationInterceptor)
928+
.build()
929+
server.enqueue(
930+
MockResponse.Builder()
931+
.build(),
932+
)
933+
val response =
934+
client
935+
.newCall(
936+
request()
937+
.build(),
938+
)
939+
.execute()
940+
response.body.close()
941+
applicationLogs
942+
.assertLogEqual("--> GET $sanitizedUrl")
943+
.assertLogMatch(Regex("""<-- 200 OK $sanitizedUrlPattern \(\d+ms, \d+-byte body\)"""))
944+
.assertNoMoreLogs()
945+
networkLogs
946+
.assertLogEqual("--> GET $sanitizedUrl http/1.1")
947+
.assertLogMatch(Regex("""<-- 200 OK $sanitizedUrlPattern \(\d+ms, \d+-byte body\)"""))
948+
.assertNoMoreLogs()
949+
}
950+
906951
@Test
907952
fun duplexRequestsAreNotLogged() {
908953
platform.assumeHttp2Support()

0 commit comments

Comments
 (0)