Skip to content

Commit

Permalink
fix: Use idp client id from remote (WPB-6494) (#2683)
Browse files Browse the repository at this point in the history
  • Loading branch information
ohassine authored and github-actions[bot] committed Feb 13, 2024
1 parent d2559d6 commit 9912c2e
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(

Check warning on line 49 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L49

Added line #L49 was not covered by tests
context: Context,
private val authUrl: String,
private val claims: JsonObject,

Check warning on line 52 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L51-L52

Added lines #L51 - L52 were not covered by tests
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

Check warning on line 66 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L65-L66

Added lines #L65 - L66 were not covered by tests
)
)
.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))

Check warning on line 77 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L77

Added line #L77 was not covered by tests

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

Check warning on line 87 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L85-L87

Added lines #L85 - L87 were not covered by tests
)
launchLoginFlow(activityResultRegistry, resultHandler)
} else {
resultHandler(OAuthResult.Success(idToken.toString(), authState.jsonSerializeString()))
resultHandler(
OAuthResult.Success(
idToken.toString(),
authState.jsonSerializeString()

Check warning on line 94 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L91-L94

Added lines #L91 - L94 were not covered by tests
)
)
}
}
}

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)

Check warning on line 110 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L110

Added line #L110 was not covered by tests

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))

Check warning on line 113 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L113

Added line #L113 was not covered by tests
) { configuration, ex ->
if (ex == null) {
authServiceConfig = configuration!!

Check warning on line 116 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L116

Added line #L116 was not covered by tests
clientId?.let {
resultLauncher.launch(getAuthorizationRequestIntent(it))

Check warning on line 118 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L118

Added line #L118 was not covered by tests
}
}, insecureConnection
)
} else {
resultHandler(OAuthResult.Failed.InvalidActivityResult("Fetching the configurations failed! $ex"))

Check warning on line 121 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L121

Added line #L121 was not covered by tests
}
}
}

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

Check warning on line 136 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L136

Added line #L136 was not covered by tests

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

Check warning on line 168 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L167-L168

Added lines #L167 - L168 were not covered by tests
).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 9912c2e

Please sign in to comment.