Skip to content

Commit

Permalink
Merge pull request #255 from uber/fixes
Browse files Browse the repository at this point in the history
Add code challenge param only with pkce authType
  • Loading branch information
lalwani authored Jul 15, 2024
2 parents 03629ad + d2af797 commit 5a0cab6
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ import com.uber.sdk2.auth.internal.sso.UniversalSsoLink.Companion.RESPONSE_TYPE
import com.uber.sdk2.auth.internal.utils.Base64Util
import com.uber.sdk2.auth.request.AuthContext
import com.uber.sdk2.auth.request.AuthType
import com.uber.sdk2.auth.request.SsoConfig
import com.uber.sdk2.auth.request.SsoConfigProvider
import com.uber.sdk2.auth.response.AuthResult
import com.uber.sdk2.auth.response.PARResponse
import com.uber.sdk2.auth.response.UberToken
import com.uber.sdk2.core.config.UriConfig
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_PARAM
import com.uber.sdk2.core.config.UriConfig.REQUEST_URI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

Expand All @@ -38,63 +42,76 @@ class AuthProvider(
private val authService: AuthService = AuthService.create(),
private val codeVerifierGenerator: PKCEGenerator = PKCEGeneratorImpl,
) : AuthProviding {

private val verifier: String = codeVerifierGenerator.generateCodeVerifier()
private val ssoLink = SsoLinkFactory.generateSsoLink(activity, authContext)

override suspend fun authenticate(): AuthResult {
val ssoConfig = withContext(Dispatchers.IO) { SsoConfigProvider.getSsoConfig(activity) }
return try {
val parResponse =
authContext.prefillInfo?.let {
val response =
authService.loginParRequest(
ssoConfig.clientId,
RESPONSE_TYPE,
Base64Util.encodePrefillInfoToString(it),
ssoConfig.scope ?: "profile",
)
val body = response.body()
body?.takeIf { response.isSuccessful }
?: throw AuthException.ServerError("Bad response ${response.code()}")
} ?: PARResponse("", "")

val queryParams =
mapOf(
"request_uri" to parResponse.requestUri,
"code_challenge" to codeVerifierGenerator.generateCodeChallenge(verifier),
)

val parResponse = sendPushedAuthorizationRequest(ssoConfig)
val queryParams = getQueryParams(parResponse)
val authCode = ssoLink.execute(queryParams)
when (authContext.authType) {
AuthType.AuthCode -> AuthResult.Success(UberToken(authCode = authCode))
is AuthType.PKCE -> {
val tokenResponse =
authService.token(
ssoConfig.clientId,
verifier,
authContext.authType.grantType,
ssoConfig.redirectUri,
authCode,
)

if (tokenResponse.isSuccessful) {
tokenResponse.body()?.let { AuthResult.Success(it) }
?: AuthResult.Error(
AuthException.ClientError("Token request failed with empty response")
)
} else {
AuthResult.Error(
AuthException.ClientError("Token request failed with code: ${tokenResponse.code()}")
)
}
}
is AuthType.PKCE -> performPkce(ssoConfig, authContext.authType, authCode)
}
} catch (e: AuthException) {
AuthResult.Error(e)
}
}

private suspend fun performPkce(
ssoConfig: SsoConfig,
authType: AuthType.PKCE,
authCode: String,
): AuthResult {
val tokenResponse =
authService.token(
ssoConfig.clientId,
verifier,
authType.grantType,
ssoConfig.redirectUri,
authCode,
)

return if (tokenResponse.isSuccessful) {
tokenResponse.body()?.let { AuthResult.Success(it) }
?: AuthResult.Error(AuthException.ClientError("Token request failed with empty response"))
} else {
AuthResult.Error(
AuthException.ClientError("Token request failed with code: ${tokenResponse.code()}")
)
}
}

private suspend fun sendPushedAuthorizationRequest(ssoConfig: SsoConfig) =
authContext.prefillInfo?.let {
val response =
authService.loginParRequest(
ssoConfig.clientId,
RESPONSE_TYPE,
Base64Util.encodePrefillInfoToString(it),
ssoConfig.scope ?: "profile",
)
val body = response.body()
body?.takeIf { response.isSuccessful }
?: throw AuthException.ServerError("Bad response ${response.code()}")
} ?: PARResponse("", "")

private fun getQueryParams(parResponse: PARResponse) = buildMap {
when (authContext.authType) {
AuthType.AuthCode -> {
parResponse.requestUri.takeIf { it.isNotEmpty() }?.let { put(REQUEST_URI, it) }
}
is AuthType.PKCE -> {
val codeChallenge = codeVerifierGenerator.generateCodeChallenge(verifier)
parResponse.requestUri.takeIf { it.isNotEmpty() }?.let { put(REQUEST_URI, it) }
put(CODE_CHALLENGE_PARAM, codeChallenge)
put(UriConfig.CODE_CHALLENGE_METHOD, UriConfig.CODE_CHALLENGE_METHOD_VAL)
}
}
}

override fun handleAuthCode(authCode: String) {
ssoLink.handleAuthCode(authCode)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ import com.uber.sdk2.auth.response.AuthResult
import com.uber.sdk2.auth.response.PARResponse
import com.uber.sdk2.auth.response.UberToken
import com.uber.sdk2.auth.sso.SsoLink
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_METHOD
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_METHOD_VAL
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_PARAM
import com.uber.sdk2.core.config.UriConfig.REQUEST_URI
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.annotation.Config
Expand All @@ -56,6 +62,7 @@ class AuthProviderTest : RobolectricTestBase() {
@Before
fun setUp() {
ssoLink = Shadow.extract<ShadowSsoLinkFactory>(SsoLinkFactory).ssoLink
reset(ssoLink)
}

@Test
Expand All @@ -71,6 +78,7 @@ class AuthProviderTest : RobolectricTestBase() {
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.PKCE(), null)
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
val result = authProvider.authenticate()
verify(ssoLink).execute(any())
verify(authService, never()).loginParRequest(any(), any(), any(), any())
verify(authService).token("clientId", "verifier", "authorization_code", "redirectUri", "code")
assert(result is AuthResult.Success)
Expand All @@ -90,6 +98,7 @@ class AuthProviderTest : RobolectricTestBase() {
val authContext =
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.PKCE(), prefillInfo)
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
val argumentCaptor = argumentCaptor<Map<String, String>>()
val result = authProvider.authenticate()
verify(authService)
.loginParRequest(
Expand All @@ -100,6 +109,11 @@ class AuthProviderTest : RobolectricTestBase() {
)
verify(authService)
.token("clientId", "verifier", "authorization_code", "redirectUri", "authCode")
verify(ssoLink).execute(argumentCaptor.capture())
assert(argumentCaptor.firstValue[REQUEST_URI] == "requestUri")
assert(argumentCaptor.firstValue[CODE_CHALLENGE_PARAM] == "challenge")
assert(argumentCaptor.firstValue[CODE_CHALLENGE_METHOD] == CODE_CHALLENGE_METHOD_VAL)
assert(argumentCaptor.firstValue.size == 3)
assert(result is AuthResult.Success)
assert((result as AuthResult.Success).uberToken.accessToken == "accessToken")
}
Expand All @@ -109,22 +123,50 @@ class AuthProviderTest : RobolectricTestBase() {
whenever(ssoLink.execute(any())).thenReturn("authCode")
whenever(authService.loginParRequest(any(), any(), any(), any()))
.thenReturn(Response.success(PARResponse("requestUri", "codeVerifier")))
val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber")
val authContext =
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.AuthCode, null)
AuthContext(
AuthDestination.CrossAppSso(listOf(CrossApp.Rider)),
AuthType.AuthCode,
prefillInfo,
)
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
val argumentCaptor = argumentCaptor<Map<String, String>>()
val result = authProvider.authenticate()
verify(authService, never()).token(any(), any(), any(), any(), any())
verify(ssoLink).execute(argumentCaptor.capture())
assert(argumentCaptor.lastValue[REQUEST_URI] == "requestUri")
assert(argumentCaptor.lastValue.size == 1)
assert(result is AuthResult.Success)
assert((result as AuthResult.Success).uberToken.authCode == "authCode")
}

@Test
fun `test authenticate when AuthCode flow and prefillInfo is Null should return only AuthCode`() =
runTest {
whenever(ssoLink.execute(any())).thenReturn("authCode")
whenever(authService.loginParRequest(any(), any(), any(), any()))
.thenReturn(Response.success(PARResponse("requestUri", "codeVerifier")))
val authContext =
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.AuthCode, null)
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
val argumentCaptor = argumentCaptor<Map<String, String>>()
val result = authProvider.authenticate()
verify(authService, never()).token(any(), any(), any(), any(), any())
verify(ssoLink).execute(argumentCaptor.capture())
assert(argumentCaptor.lastValue.isEmpty())
assert(result is AuthResult.Success)
assert((result as AuthResult.Success).uberToken.authCode == "authCode")
}

@Test
fun `test authenticate when authException should return error result`() = runTest {
whenever(ssoLink.execute(any())).thenThrow(AuthException.ClientError("error"))
val authContext =
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.AuthCode, null)
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
val result = authProvider.authenticate()
verify(ssoLink).execute(any())
assert(result is AuthResult.Error && result.authException.message == "error")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ object UriConfig {
.appendQueryParameter(SCOPE_PARAM, scopes)
.appendQueryParameter(SDK_VERSION_PARAM, BuildConfig.VERSION_NAME)
.appendQueryParameter(PLATFORM_PARAM, "android")
.appendQueryParameter(CODE_CHALLENGE_METHOD, CODE_CHALLENGE_METHOD_VAL)
return builder.build()
}

Expand Down
3 changes: 0 additions & 3 deletions core/src/test/kotlin/com/uber/sdk2/core/UriConfigTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package com.uber.sdk2.core

import com.uber.sdk2.core.config.UriConfig
import com.uber.sdk2.core.config.UriConfig.CLIENT_ID_PARAM
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_METHOD
import com.uber.sdk2.core.config.UriConfig.CODE_CHALLENGE_METHOD_VAL
import com.uber.sdk2.core.config.UriConfig.PLATFORM_PARAM
import com.uber.sdk2.core.config.UriConfig.REDIRECT_PARAM
import com.uber.sdk2.core.config.UriConfig.RESPONSE_TYPE_PARAM
Expand Down Expand Up @@ -47,7 +45,6 @@ class UriConfigTest : RobolectricTestBase() {
assertEquals(scopes, uri.getQueryParameter(SCOPE_PARAM))
assertEquals(BuildConfig.VERSION_NAME, uri.getQueryParameter(SDK_VERSION_PARAM))
assertEquals("android", uri.getQueryParameter(PLATFORM_PARAM))
assertEquals(CODE_CHALLENGE_METHOD_VAL, uri.getQueryParameter(CODE_CHALLENGE_METHOD))
}

@Test
Expand Down

0 comments on commit 5a0cab6

Please sign in to comment.