Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,7 @@ private String refreshToken(
return null;
}

Timber.d("Ready to exchange for new tokens. Account: [ %s ], Refresh token: [ %s ]", account.name,
refreshToken);
Timber.d("Ready to exchange refresh token for new tokens. Account: [ %s ]", account.name);

String baseUrl = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);

Expand Down Expand Up @@ -440,8 +439,7 @@ private String handleSuccessfulRefreshToken(
}
accountManager.setUserData(account, KEY_OAUTH2_REFRESH_TOKEN, refreshTokenToUseFromNowOn);

Timber.d("Token refreshed successfully. New access token: [ %s ]. New refresh token: [ %s ]",
newAccessToken, refreshTokenToUseFromNowOn);
Timber.d("Token refreshed successfully for account [ %s ]", account.name);

return newAccessToken;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Timber.d("onCreate called with intent data: ${intent.data}, isTaskRoot: $isTaskRoot")
Timber.d("onCreate called with intent data present: ${intent.data != null}, isTaskRoot: $isTaskRoot")

if (handleOAuthRedirectOnCreate()) return

Expand Down Expand Up @@ -747,7 +747,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
Timber.d("onNewIntent received with data: ${it.data}")
Timber.d("onNewIntent received with data present: ${it.data != null}")
if (!::binding.isInitialized) {
Timber.w("onNewIntent received before binding initialized, ignoring OAuth response")
return
Expand All @@ -762,12 +762,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
val state = intent.data?.getQueryParameter("state")

if (state != authenticationViewModel.oidcState) {
Timber.e("OAuth: state mismatch (expected=${authenticationViewModel.oidcState}, got=$state). Finishing.")
Timber.e("OAuth state mismatch. Finishing.")
showMessageInSnackbar(message = getString(R.string.auth_oauth_error))
finish()
} else {
if (authorizationCode != null) {
Timber.d("Authorization code received [$authorizationCode]. Let's exchange it for access token")
Timber.d("Authorization code received. Exchanging it for access token")
exchangeAuthorizationCodeForTokens(authorizationCode)
} else {
val authorizationError = intent.data?.getQueryParameter("error")
Expand Down Expand Up @@ -851,12 +851,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
when (val uiResult = it.peekContent()) {
is UIResult.Loading -> {}
is UIResult.Success -> {
Timber.d("Tokens received ${uiResult.data}, trying to login, creating account and adding it to account manager")
Timber.d("Tokens received, trying to login, creating account and adding it to account manager")
val tokenResponse = uiResult.data ?: return@observe

// Extract preferred_username from id_token for login_hint on re-login
preferredUsername = extractPreferredUsernameFromIdToken(tokenResponse.idToken)
Timber.d("Preferred username from id_token: $preferredUsername")
Timber.d("Preferred username extracted from id_token: ${preferredUsername != null}")

// When webfinger provides a client_id without dynamic registration,
// store it so AccountAuthenticator can use it for token refresh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import eu.opencloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Request
Expand Down Expand Up @@ -74,7 +75,7 @@ class LogInterceptor : Interceptor {
info = RequestInfo(
id = requestId,
method = request.method,
url = request.url.toString(),
url = redactUrl(request.url),
)
)
)
Expand All @@ -88,10 +89,16 @@ class LogInterceptor : Interceptor {
if (auxHeaders.contains(AUTHORIZATION_HEADER)) {
val authHeaderList = auxHeaders[AUTHORIZATION_HEADER]!!.split(" ")
val authType = authHeaderList[0]
val authInfo = if (redactAuthHeader) "[redacted]" else authHeaderList[1]
val authInfo = if (redactAuthHeader) REDACTED else authHeaderList.getOrNull(1).orEmpty()
auxHeaders[AUTHORIZATION_HEADER] = "$authType $authInfo"
}
return auxHeaders
return auxHeaders.mapValues { (header, value) ->
if (SENSITIVE_HEADER_NAMES.any { it.equals(header, ignoreCase = true) }) {
REDACTED
} else {
redactSensitiveData(value)
}
}
}

private fun getRequestBodyString(requestBodyParam: RequestBody?): String? {
Expand All @@ -110,7 +117,7 @@ class LogInterceptor : Interceptor {
val contentType = requestBody.contentType()
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
if (contentType.isLoggable()) {
return buffer.readString(charset)
return redactSensitiveData(buffer.readString(charset))
} else if (requestBody.contentLength() > 0) {
return "$BINARY_OMITTED ${requestBody.contentLength()} $BYTES"
}
Expand Down Expand Up @@ -147,7 +154,7 @@ class LogInterceptor : Interceptor {
status = response.code,
version = response.protocol.toString(),
),
url = request.url.toString(),
url = redactUrl(request.url),
)
)
)
Expand All @@ -158,7 +165,7 @@ class LogInterceptor : Interceptor {

private fun getResponseBodyString(contentType: MediaType?, contentLength: Int, responseBody: String): String? =
if (contentType?.isLoggable() == true) {
responseBody
redactSensitiveData(responseBody)
} else if (contentLength > 0) {
"$BINARY_OMITTED $contentLength $BYTES"
} else {
Expand All @@ -176,11 +183,63 @@ class LogInterceptor : Interceptor {
return String.format(DURATION_FORMAT, hours, minutes, seconds, auxMillis)
}

private fun redactUrl(url: HttpUrl): String {
var redactedUrl = url.newBuilder()
url.queryParameterNames
.filter(::isSensitiveField)
.forEach { redactedUrl = redactedUrl.setQueryParameter(it, REDACTED) }
return redactedUrl.build().toString()
}

private fun redactSensitiveData(value: String): String =
FORM_FIELD_REGEX.replace(
JSON_FIELD_REGEX.replace(
TO_STRING_FIELD_REGEX.replace(value) {
"${it.groupValues[1]}$REDACTED"
}
) {
"${it.groupValues[1]}$REDACTED${it.groupValues[4]}"
}
) {
"${it.groupValues[1]}${it.groupValues[2]}=$REDACTED"
}

private fun isSensitiveField(field: String): Boolean =
SENSITIVE_FIELD_NAMES.any { it.equals(field, ignoreCase = true) }

companion object {
var httpLogsEnabled: Boolean = false
var redactAuthHeader: Boolean = true
private const val REDACTED = "[redacted]"
private const val LIMIT_BODY_LOG: Long = 1000000
private const val BINARY_OMITTED = "<-- Body end for response -- Binary -- Omitted:"
private const val BYTES = "bytes -->"
private val SENSITIVE_HEADER_NAMES = setOf(
"Cookie",
"Set-Cookie",
"X-Auth-Token",
)
private val SENSITIVE_FIELD_NAMES = setOf(
"access_token",
"accessToken",
"authorization_code",
"authorizationCode",
"client_secret",
"clientSecret",
"code",
"code_verifier",
"codeVerifier",
"id_token",
"idToken",
"password",
"passcode",
"pattern",
"refresh_token",
"refreshToken",
)
private val SENSITIVE_FIELD_PATTERN = SENSITIVE_FIELD_NAMES.joinToString("|") { Regex.escape(it) }
private val JSON_FIELD_REGEX = Regex("""(?i)("($SENSITIVE_FIELD_PATTERN)"\s*:\s*")([^"]*)(")""")
private val FORM_FIELD_REGEX = Regex("""(?i)(^|[?&])($SENSITIVE_FIELD_PATTERN)=([^&#]*)""")
private val TO_STRING_FIELD_REGEX = Regex("""(?i)(\b($SENSITIVE_FIELD_PATTERN)\s*=\s*)([^,)\s&]+)""")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,20 @@ class TokenRequestRemoteOperation(
val responseBody = postMethod.getResponseBodyAsString()

return if (status == HTTP_OK && responseBody != null) {
Timber.d("Successful response $responseBody")
Timber.d("Successful token response received")

// Parse the response
val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<TokenResponse> = moshi.adapter(TokenResponse::class.java)
val tokenResponse: TokenResponse? = jsonAdapter.fromJson(responseBody)
Timber.d("Get tokens completed and parsed to $tokenResponse")
Timber.d("Get tokens completed and parsed")

RemoteOperationResult<TokenResponse>(RemoteOperationResult.ResultCode.OK).apply {
data = tokenResponse
}

} else {
Timber.e("Failed response while getting tokens from the server status code: $status; response message: $responseBody")
Timber.e("Failed response while getting tokens from the server status code: $status")
RemoteOperationResult<TokenResponse>(postMethod)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,22 @@ data class TokenResponse(
val idToken: String? = null,
@Json(name = "additional_parameters")
val additionalParameters: Map<String, String>?
)
) {
override fun toString(): String =
"TokenResponse(" +
"accessToken=$REDACTED, " +
"expiresIn=$expiresIn, " +
"refreshToken=${refreshToken.redactedOrNull()}, " +
"tokenType=$tokenType, " +
"userId=$userId, " +
"scope=$scope, " +
"idToken=${idToken.redactedOrNull()}, " +
"additionalParameters=$additionalParameters" +
")"

private fun String?.redactedOrNull(): String = if (this == null) "null" else REDACTED

private companion object {
const val REDACTED = "[redacted]"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,22 @@ data class TokenResponse(
val scope: String?,
val idToken: String? = null,
val additionalParameters: Map<String, String>?
)
) {
override fun toString(): String =
"TokenResponse(" +
"accessToken=$REDACTED, " +
"expiresIn=$expiresIn, " +
"refreshToken=${refreshToken.redactedOrNull()}, " +
"tokenType=$tokenType, " +
"userId=$userId, " +
"scope=$scope, " +
"idToken=${idToken.redactedOrNull()}, " +
"additionalParameters=$additionalParameters" +
")"

private fun String?.redactedOrNull(): String = if (this == null) "null" else REDACTED

private companion object {
const val REDACTED = "[redacted]"
}
}
Loading