Skip to content

Commit

Permalink
Merge branch 'release/candidate' into crash_answering_call_rc
Browse files Browse the repository at this point in the history
  • Loading branch information
ohassine committed Feb 14, 2024
2 parents 85b5289 + a6b9a5f commit 3b90202
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 59 deletions.
105 changes: 46 additions & 59 deletions app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Base64
import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContracts
import com.wire.android.appLogger
import com.wire.android.util.deeplink.DeepLinkProcessor
import com.wire.android.util.findParameterValue
import com.wire.android.util.removeQueryParams
import kotlinx.serialization.json.JsonObject
import net.openid.appauth.AppAuthConfiguration
Expand All @@ -38,99 +38,89 @@ import net.openid.appauth.AuthorizationResponse
import net.openid.appauth.AuthorizationService
import net.openid.appauth.AuthorizationServiceConfiguration
import net.openid.appauth.ClientAuthentication
import net.openid.appauth.ClientSecretBasic
import net.openid.appauth.ResponseTypeValues
import net.openid.appauth.browser.BrowserAllowList
import net.openid.appauth.browser.VersionedBrowserMatcher
import net.openid.appauth.connectivity.ConnectionBuilder
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URI
import java.net.URL
import java.security.MessageDigest
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

class OAuthUseCase(context: Context, private val authUrl: String, private val claims: JsonObject, oAuthState: String?) {

class OAuthUseCase(
context: Context,
private val authUrl: String,
private val claims: JsonObject,
oAuthState: String?
) {
private var authState: AuthState = oAuthState?.let {
AuthState.jsonDeserialize(it)
} ?: AuthState()

private var authorizationService: AuthorizationService
private lateinit var authServiceConfig: AuthorizationServiceConfiguration

// todo: this is a temporary code to ignore ssl issues on the test environment, will be removed after the preparation of the environment
// region Ignore SSL for OAuth
val naiveTrustManager = object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
}
val insecureSocketFactory = SSLContext.getInstance("SSL").apply {
val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
init(null, trustAllCerts, SecureRandom())
}.socketFactory

private var insecureConnection = ConnectionBuilder() { uri ->
val url = URL(uri.toString())
val connection = url.openConnection() as HttpURLConnection
if (connection is HttpsURLConnection) {
connection.hostnameVerifier = HostnameVerifier { _, _ -> true }
connection.sslSocketFactory = insecureSocketFactory
}
connection
}
// endregion

private var appAuthConfiguration: AppAuthConfiguration = AppAuthConfiguration.Builder()
.setBrowserMatcher(
BrowserAllowList(
VersionedBrowserMatcher.CHROME_CUSTOM_TAB, VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB
VersionedBrowserMatcher.CHROME_CUSTOM_TAB,
VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB
)
)
.setConnectionBuilder(insecureConnection)
.setSkipIssuerHttpsCheck(true)
.build()

init {
authorizationService = AuthorizationService(context, appAuthConfiguration)
}

private fun getAuthorizationRequestIntent(): Intent = authorizationService.getAuthorizationRequestIntent(getAuthorizationRequest())
private fun getAuthorizationRequestIntent(clientId: String): Intent =
authorizationService.getAuthorizationRequestIntent(getAuthorizationRequest(clientId))

fun launch(activityResultRegistry: ActivityResultRegistry, resultHandler: (OAuthResult) -> Unit) {
fun launch(
activityResultRegistry: ActivityResultRegistry,
resultHandler: (OAuthResult) -> Unit
) {
authState.performActionWithFreshTokens(authorizationService) { _, idToken, exception ->
if (exception != null) {
Log.e("OAuthTokenRefreshManager", "Error refreshing tokens, continue with login!", exception)
appLogger.e(
message = "OAuthTokenRefreshManager: Error refreshing tokens, continue with login!",
throwable = exception
)
launchLoginFlow(activityResultRegistry, resultHandler)
} else {
resultHandler(OAuthResult.Success(idToken.toString(), authState.jsonSerializeString()))
resultHandler(
OAuthResult.Success(
idToken.toString(),
authState.jsonSerializeString()
)
)
}
}
}

private fun launchLoginFlow(activityResultRegistry: ActivityResultRegistry, resultHandler: (OAuthResult) -> Unit) {
private fun launchLoginFlow(
activityResultRegistry: ActivityResultRegistry,
resultHandler: (OAuthResult) -> Unit
) {
val resultLauncher = activityResultRegistry.register(
OAUTH_ACTIVITY_RESULT_KEY, ActivityResultContracts.StartActivityForResult()
) { result ->
handleActivityResult(result, resultHandler)
}
val clientId = URI(authUrl).findParameterValue(CLIENT_ID_QUERY_PARAM)

AuthorizationServiceConfiguration.fetchFromUrl(
Uri.parse(URI(authUrl).removeQueryParams().toString().plus(IDP_CONFIGURATION_PATH)),
{ configuration, ex ->
if (ex == null) {
authServiceConfig = configuration!!
resultLauncher.launch(getAuthorizationRequestIntent())
} else {
resultHandler(OAuthResult.Failed.InvalidActivityResult("Fetching the configurations failed! $ex"))
Uri.parse(URI(authUrl).removeQueryParams().toString().plus(IDP_CONFIGURATION_PATH))
) { configuration, ex ->
if (ex == null) {
authServiceConfig = configuration!!
clientId?.let {
resultLauncher.launch(getAuthorizationRequestIntent(it))
}
}, insecureConnection
)
} else {
resultHandler(OAuthResult.Failed.InvalidActivityResult("Fetching the configurations failed! $ex"))
}
}
}

private fun handleActivityResult(result: ActivityResult, resultHandler: (OAuthResult) -> Unit) {
Expand All @@ -143,7 +133,7 @@ class OAuthUseCase(context: Context, private val authUrl: String, private val cl

private fun handleAuthorizationResponse(intent: Intent, resultHandler: (OAuthResult) -> Unit) {
val authorizationResponse: AuthorizationResponse? = AuthorizationResponse.fromIntent(intent)
val clientAuth: ClientAuthentication = ClientSecretBasic(CLIENT_SECRET)
val clientAuth: ClientAuthentication = AuthState().clientAuthentication

val error = AuthorizationException.fromIntent(intent)

Expand Down Expand Up @@ -174,8 +164,8 @@ class OAuthUseCase(context: Context, private val authUrl: String, private val cl
} ?: resultHandler(OAuthResult.Failed.Unknown)
}

private fun getAuthorizationRequest() = AuthorizationRequest.Builder(
authServiceConfig, CLIENT_ID, ResponseTypeValues.CODE, URL_AUTH_REDIRECT
private fun getAuthorizationRequest(clientId: String) = AuthorizationRequest.Builder(
authServiceConfig, clientId, ResponseTypeValues.CODE, URL_AUTH_REDIRECT
).setCodeVerifier().setScopes(
AuthorizationRequest.Scope.OPENID,
AuthorizationRequest.Scope.EMAIL,
Expand Down Expand Up @@ -216,10 +206,7 @@ class OAuthUseCase(context: Context, private val authUrl: String, private val cl

companion object {
const val OAUTH_ACTIVITY_RESULT_KEY = "OAuthActivityResult"

// todo: clientId and the clientSecret will be replaced with the values from the BE once the BE provides them
const val CLIENT_ID = "wireapp"
const val CLIENT_SECRET = "dUpVSGx2dVdFdGQ0dmsxWGhDalQ0SldU"
const val CLIENT_ID_QUERY_PARAM = "client_id"
const val CODE_VERIFIER_CHALLENGE_METHOD = "S256"
const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
val MESSAGE_DIGEST = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/kotlin/com/wire/android/util/UriUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,18 @@ fun URI.removeQueryParams(): URI {
val regex = Regex("[?&][^=]+=[^&]*")
return URI(this.toString().replace(regex, ""))
}

@Suppress("TooGenericExceptionCaught")
fun URI.findParameterValue(parameterName: String): String? {
return try {
rawQuery.split('&').map {
val parts = it.split('=')
val name = parts.firstOrNull() ?: ""
val value = parts.drop(1).firstOrNull() ?: ""
Pair(name, value)
}.firstOrNull { it.first == parameterName }?.second
} catch (e: NullPointerException) {
appLogger.w("Error finding parameter value: $parameterName", e)
null
}
}
23 changes: 23 additions & 0 deletions app/src/test/kotlin/com/wire/android/util/UriUtilTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.android.util
import com.wire.android.string
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test
import java.net.URI
import kotlin.random.Random

class UriUtilTest {
Expand Down Expand Up @@ -86,4 +87,26 @@ class UriUtilTest {
val actual = normalizeLink(input)
assertEquals(input, actual)
}

@Test
fun givenLinkWithQueryParams_whenCallingFindParameterValue_thenReturnsParamValue() {
val parameterName = "wire_client"
val parameterValue = "value1"
val url = "https://example.com?play=value&$parameterName=$parameterValue"
val actual = URI(url).findParameterValue(parameterName)
assertEquals(parameterValue, actual)
}

@Test
fun givenLinkWithoutRequestedParam_whenCallingFindParameterValue_thenReturnsParamValue() {
val url = "https://example.com?play=value1"
val actual = URI(url).findParameterValue("wire_client")
assertEquals(null, actual)
}
@Test
fun givenLinkWithoutParams_whenCallingFindParameterValue_thenReturnsParamValue() {
val url = "https://example.com"
val actual = URI(url).findParameterValue("wire_client")
assertEquals(null, actual)
}
}

0 comments on commit 3b90202

Please sign in to comment.