@@ -28,6 +28,7 @@ import eu.opencloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
2828import com.squareup.moshi.Moshi
2929import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
3030import okhttp3.Headers
31+ import okhttp3.HttpUrl
3132import okhttp3.Interceptor
3233import okhttp3.MediaType
3334import okhttp3.Request
@@ -74,7 +75,7 @@ class LogInterceptor : Interceptor {
7475 info = RequestInfo (
7576 id = requestId,
7677 method = request.method,
77- url = request.url.toString( ),
78+ url = redactUrl( request.url),
7879 )
7980 )
8081 )
@@ -88,10 +89,16 @@ class LogInterceptor : Interceptor {
8889 if (auxHeaders.contains(AUTHORIZATION_HEADER )) {
8990 val authHeaderList = auxHeaders[AUTHORIZATION_HEADER ]!! .split(" " )
9091 val authType = authHeaderList[0 ]
91- val authInfo = if (redactAuthHeader) " [redacted] " else authHeaderList[ 1 ]
92+ val authInfo = if (redactAuthHeader) REDACTED else authHeaderList.getOrNull( 1 ).orEmpty()
9293 auxHeaders[AUTHORIZATION_HEADER ] = " $authType $authInfo "
9394 }
94- return auxHeaders
95+ return auxHeaders.mapValues { (header, value) ->
96+ if (SENSITIVE_HEADER_NAMES .any { it.equals(header, ignoreCase = true ) }) {
97+ REDACTED
98+ } else {
99+ redactSensitiveData(value)
100+ }
101+ }
95102 }
96103
97104 private fun getRequestBodyString (requestBodyParam : RequestBody ? ): String? {
@@ -110,7 +117,7 @@ class LogInterceptor : Interceptor {
110117 val contentType = requestBody.contentType()
111118 val charset: Charset = contentType?.charset(StandardCharsets .UTF_8 ) ? : StandardCharsets .UTF_8
112119 if (contentType.isLoggable()) {
113- return buffer.readString(charset)
120+ return redactSensitiveData( buffer.readString(charset) )
114121 } else if (requestBody.contentLength() > 0 ) {
115122 return " $BINARY_OMITTED ${requestBody.contentLength()} $BYTES "
116123 }
@@ -147,7 +154,7 @@ class LogInterceptor : Interceptor {
147154 status = response.code,
148155 version = response.protocol.toString(),
149156 ),
150- url = request.url.toString( ),
157+ url = redactUrl( request.url),
151158 )
152159 )
153160 )
@@ -158,7 +165,7 @@ class LogInterceptor : Interceptor {
158165
159166 private fun getResponseBodyString (contentType : MediaType ? , contentLength : Int , responseBody : String ): String? =
160167 if (contentType?.isLoggable() == true ) {
161- responseBody
168+ redactSensitiveData( responseBody)
162169 } else if (contentLength > 0 ) {
163170 " $BINARY_OMITTED $contentLength $BYTES "
164171 } else {
@@ -176,11 +183,63 @@ class LogInterceptor : Interceptor {
176183 return String .format(DURATION_FORMAT , hours, minutes, seconds, auxMillis)
177184 }
178185
186+ private fun redactUrl (url : HttpUrl ): String {
187+ var redactedUrl = url.newBuilder()
188+ url.queryParameterNames
189+ .filter(::isSensitiveField)
190+ .forEach { redactedUrl = redactedUrl.setQueryParameter(it, REDACTED ) }
191+ return redactedUrl.build().toString()
192+ }
193+
194+ private fun redactSensitiveData (value : String ): String =
195+ FORM_FIELD_REGEX .replace(
196+ JSON_FIELD_REGEX .replace(
197+ TO_STRING_FIELD_REGEX .replace(value) {
198+ " ${it.groupValues[1 ]}$REDACTED "
199+ }
200+ ) {
201+ " ${it.groupValues[1 ]}$REDACTED${it.groupValues[4 ]} "
202+ }
203+ ) {
204+ " ${it.groupValues[1 ]}${it.groupValues[2 ]} =$REDACTED "
205+ }
206+
207+ private fun isSensitiveField (field : String ): Boolean =
208+ SENSITIVE_FIELD_NAMES .any { it.equals(field, ignoreCase = true ) }
209+
179210 companion object {
180211 var httpLogsEnabled: Boolean = false
181212 var redactAuthHeader: Boolean = true
213+ private const val REDACTED = " [redacted]"
182214 private const val LIMIT_BODY_LOG : Long = 1000000
183215 private const val BINARY_OMITTED = " <-- Body end for response -- Binary -- Omitted:"
184216 private const val BYTES = " bytes -->"
217+ private val SENSITIVE_HEADER_NAMES = setOf (
218+ " Cookie" ,
219+ " Set-Cookie" ,
220+ " X-Auth-Token" ,
221+ )
222+ private val SENSITIVE_FIELD_NAMES = setOf (
223+ " access_token" ,
224+ " accessToken" ,
225+ " authorization_code" ,
226+ " authorizationCode" ,
227+ " client_secret" ,
228+ " clientSecret" ,
229+ " code" ,
230+ " code_verifier" ,
231+ " codeVerifier" ,
232+ " id_token" ,
233+ " idToken" ,
234+ " password" ,
235+ " passcode" ,
236+ " pattern" ,
237+ " refresh_token" ,
238+ " refreshToken" ,
239+ )
240+ private val SENSITIVE_FIELD_PATTERN = SENSITIVE_FIELD_NAMES .joinToString(" |" ) { Regex .escape(it) }
241+ private val JSON_FIELD_REGEX = Regex (""" (?i)("($SENSITIVE_FIELD_PATTERN )"\s*:\s*")([^"]*)(")""" )
242+ private val FORM_FIELD_REGEX = Regex (""" (?i)(^|[?&])($SENSITIVE_FIELD_PATTERN )=([^&#]*)""" )
243+ private val TO_STRING_FIELD_REGEX = Regex (""" (?i)(\b($SENSITIVE_FIELD_PATTERN )\s*=\s*)([^,)\s&]+)""" )
185244 }
186245}
0 commit comments