From 20f52bf3621f9c28aabf442acb7eb8fa43299897 Mon Sep 17 00:00:00 2001 From: Rajdeep Nanua Date: Thu, 13 Jun 2024 15:43:04 -0400 Subject: [PATCH 1/3] Add device token cookie to IDX --- README.md | 7 +- gradle/libs.versions.toml | 5 + okta-idx-kotlin/api/okta-idx-kotlin.api | 14 +++ okta-idx-kotlin/build.gradle | 4 + .../idx/kotlin/client/DeviceTokenCookieJar.kt | 55 +++++++++++ .../idx/kotlin/client/DeviceTokenProvider.kt | 72 ++++++++++++++ .../idx/kotlin/client/InteractionCodeFlow.kt | 3 +- .../client/LegacyDeviceTokenProvider.kt | 72 ++++++++++++++ .../kotlin/client/DeviceTokenProviderTest.kt | 97 +++++++++++++++++++ 9 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenCookieJar.kt create mode 100644 okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenProvider.kt create mode 100644 okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/LegacyDeviceTokenProvider.kt create mode 100644 okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/client/DeviceTokenProviderTest.kt diff --git a/README.md b/README.md index b60f16b..9e4cb7b 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,11 @@ The `InteractionCodeFlow` class is used to define and initiate an authentication This class makes heavy use of [Kotlin Coroutines][kotlin-coroutines] to perform the actions asynchronously. -#### OidcClient.createInteractionCodeFlow -The `createInteractionCodeFlow` extension method on `OidcClient` is used to create an `InteractionCodeFlow`, and to start an authorization flow. +#### Constructing InteractionCodeFlow +`InteractionCodeFlow` can be instantiated with `OAuth2Client.default` by using the default constructor `InteractionCodeFlow()`. Alternatively, a non-default `OAuth2Client` or `OidcConfiguration` can be provided. + +#### InteractionCodeFlow.start +The `start` method starts the authentication flow, and returns the result as `OAuth2ClientResult`. The result is empty on success, and an error in form `OAuth2ClientResult.Error` in case of an error. #### InteractionCodeFlow.resume The `resume` method on an `InteractionCodeFlow` is used to reveal the current remediations. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56cc4f8..b545625 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] authFoundation = "2.0.1" androidxConstraintlayout = "2.1.4" +androidxDatastorePreferences = "1.1.1" androidxTest = "1.5.0" androidxTestRunner = "1.5.2" androidxTestExt = "1.1.5" @@ -32,12 +33,14 @@ okhttp = "4.12.0" okio = "3.9.0" oktaManagement = "14.0.0" robolectric = "4.12.2" +securityCrypto = "1.0.0" spotless = "6.7.0" timber = "5.0.1" truth = "1.1.5" [libraries] androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidxConstraintlayout" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidxDatastorePreferences" } androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTest" } androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExt" } @@ -112,6 +115,8 @@ okta-management-sdk = { module = "com.okta.sdk:okta-sdk-impl", version.ref = "ok robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" } + spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } diff --git a/okta-idx-kotlin/api/okta-idx-kotlin.api b/okta-idx-kotlin/api/okta-idx-kotlin.api index 74aed44..a14905f 100644 --- a/okta-idx-kotlin/api/okta-idx-kotlin.api +++ b/okta-idx-kotlin/api/okta-idx-kotlin.api @@ -1,3 +1,17 @@ +public final class com/okta/authfoundation/client/DeviceTokenProvider { + public static final field Companion Lcom/okta/authfoundation/client/DeviceTokenProvider$Companion; + public static final field PREFERENCE_NAME Ljava/lang/String; + public fun ()V + public fun (Lcom/okta/authfoundation/util/AesEncryptionHandler;)V + public synthetic fun (Lcom/okta/authfoundation/util/AesEncryptionHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getDeviceToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/okta/authfoundation/client/DeviceTokenProvider$Companion { + public final fun getInstance ()Lcom/okta/authfoundation/client/DeviceTokenProvider; + public final fun getPREFERENCE_KEY ()Landroidx/datastore/preferences/core/Preferences$Key; +} + public abstract class com/okta/idx/kotlin/client/IdxRedirectResult { } diff --git a/okta-idx-kotlin/build.gradle b/okta-idx-kotlin/build.gradle index 3b67556..612453f 100644 --- a/okta-idx-kotlin/build.gradle +++ b/okta-idx-kotlin/build.gradle @@ -56,9 +56,13 @@ dependencies { api libs.coroutines.android api libs.okta.auth.foundation + implementation libs.androidx.datastore.preferences implementation libs.kotlin.serialization.json implementation libs.okio.jvm + implementation libs.security.crypto + testImplementation libs.androidx.test.ext.junit + testImplementation libs.coroutines.test testImplementation libs.junit testImplementation libs.okhttp.mock.web.server testImplementation libs.okhttp.tls diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenCookieJar.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenCookieJar.kt new file mode 100644 index 0000000..636dab5 --- /dev/null +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenCookieJar.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.idx.kotlin.client + +import com.okta.authfoundation.AuthFoundationDefaults +import com.okta.authfoundation.client.DeviceTokenProvider +import kotlinx.coroutines.runBlocking +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import kotlin.time.Duration.Companion.seconds + +internal class DeviceTokenCookieJar : CookieJar { + private val savedCookiesCache = mutableMapOf>() + private val oidcClock by lazy { AuthFoundationDefaults.clock } + + private val deviceTokenCookieBuilder: Cookie.Builder + get() { + val deviceToken = runBlocking { DeviceTokenProvider.instance.getDeviceToken() } + return getDtCookieBuilderWith(deviceToken) + } + + private fun getDtCookieBuilderWith(deviceToken: String): Cookie.Builder { + return Cookie.Builder() + .name("DT") + .value(deviceToken) + .secure() + } + + override fun loadForRequest(url: HttpUrl): List { + val deviceTokenCookie = deviceTokenCookieBuilder.domain(url.host).build() + val savedCookiesForDomain = savedCookiesCache[url.host]?.filter { + it.expiresAt > oidcClock.currentTimeEpochSecond().seconds.inWholeMilliseconds + } ?: emptyList() + val deviceTokenCookieList = listOf(deviceTokenCookie) + return savedCookiesForDomain + deviceTokenCookieList + } + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + savedCookiesCache[url.host] = cookies + } +} diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenProvider.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenProvider.kt new file mode 100644 index 0000000..f6b1e83 --- /dev/null +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/DeviceTokenProvider.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2024-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.authfoundation.client + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.okta.authfoundation.InternalAuthFoundationApi +import com.okta.authfoundation.util.AesEncryptionHandler +import com.okta.idx.kotlin.client.LegacyDeviceTokenProvider +import kotlinx.coroutines.flow.firstOrNull +import java.util.UUID + +@InternalAuthFoundationApi +class DeviceTokenProvider( + private val aesEncryptionHandler: AesEncryptionHandler = AesEncryptionHandler() +) { + companion object { + const val PREFERENCE_NAME = "com.okta.idx.client.deviceToken" + val PREFERENCE_KEY = stringPreferencesKey("encryptedDeviceToken") + val instance by lazy { DeviceTokenProvider() } + } + + private val Context.dataStore: DataStore by preferencesDataStore(PREFERENCE_NAME) + private val context by lazy { ApplicationContextHolder.appContext } + + suspend fun getDeviceToken(): String { + val encryptedDeviceToken = context.dataStore.data.firstOrNull()?.get(PREFERENCE_KEY) + return encryptedDeviceToken?.let { + aesEncryptionHandler.decryptString(it) + } ?: run { + val deviceToken = getLegacyDeviceToken() ?: createNewDeviceToken() + setDeviceToken(deviceToken) + deviceToken + } + } + + private fun getLegacyDeviceToken(): String? { + return try { + val legacyDeviceTokenProvider = LegacyDeviceTokenProvider(context) + if (legacyDeviceTokenProvider.containsDeviceToken()) { + legacyDeviceTokenProvider.deviceToken + } else null + } catch (e: Exception) { + null + } + } + + private fun createNewDeviceToken(): String = + UUID.randomUUID().toString().filter { it.isLetterOrDigit() } + + private suspend fun setDeviceToken(deviceToken: String) = + context.dataStore.edit { preferences -> + preferences[PREFERENCE_KEY] = aesEncryptionHandler.encryptString(deviceToken) + } +} diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt index 0b51a41..91ce2d9 100644 --- a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt @@ -41,10 +41,11 @@ import com.okta.idx.kotlin.dto.v1.Response as V1Response /** * The InteractionCodeFlow class is used to define and initiate an authentication workflow utilizing the Okta Identity Engine. */ -class InteractionCodeFlow constructor(private val client: OAuth2Client) { +class InteractionCodeFlow(private val client: OAuth2Client) { companion object { init { SdkVersionsRegistry.register(SDK_VERSION) + AuthFoundationDefaults.cookieJar = DeviceTokenCookieJar() } } diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/LegacyDeviceTokenProvider.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/LegacyDeviceTokenProvider.kt new file mode 100644 index 0000000..0aa24ac --- /dev/null +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/LegacyDeviceTokenProvider.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2024-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.idx.kotlin.client + +import android.content.Context +import android.content.SharedPreferences +import androidx.annotation.VisibleForTesting +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys +import java.util.UUID + +internal class LegacyDeviceTokenProvider(private val appContext: Context) { + internal companion object { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal const val FILE_NAME = "com.okta.authfoundation.device_token_storage" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal const val PREFERENCE_KEY = "com.okta.authfoundation.device_token_key" + } + + private val masterKeyAlias by lazy { MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) } + + private fun createSharedPreferences(): SharedPreferences { + return EncryptedSharedPreferences.create( + FILE_NAME, + masterKeyAlias, + appContext, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val sharedPrefs: SharedPreferences by lazy { + try { + createSharedPreferences() + } catch (e: Exception) { + val sharedPreferences = appContext.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) + sharedPreferences.edit().clear().commit() + createSharedPreferences() + } + } + + private val sharedPrefsEditor by lazy { sharedPrefs.edit() } + + internal val deviceTokenUUID: String + get() { + return sharedPrefs.getString(PREFERENCE_KEY, null) ?: run { + val newDeviceToken = UUID.randomUUID().toString() + sharedPrefsEditor.putString(PREFERENCE_KEY, newDeviceToken) + sharedPrefsEditor.commit() + newDeviceToken + } + } + + internal fun containsDeviceToken(): Boolean = sharedPrefs.contains(PREFERENCE_KEY) + + internal val deviceToken: String + get() = deviceTokenUUID.filter { it.isLetterOrDigit() } +} diff --git a/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/client/DeviceTokenProviderTest.kt b/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/client/DeviceTokenProviderTest.kt new file mode 100644 index 0000000..d671d35 --- /dev/null +++ b/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/client/DeviceTokenProviderTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2024-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.idx.kotlin.client + +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.okta.authfoundation.client.ApplicationContextHolder +import com.okta.authfoundation.client.DeviceTokenProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.unmockkStatic +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.UUID + +@RunWith(AndroidJUnit4::class) +class DeviceTokenProviderTest { + private val uuid = UUID.randomUUID() + private val deviceToken = uuid.toString().filter { it.isLetterOrDigit() } + private lateinit var deviceTokenProvider: DeviceTokenProvider + + @Before + fun setup() { + unmockkAll() + mockkObject(ApplicationContextHolder) + every { ApplicationContextHolder.appContext } returns ApplicationProvider.getApplicationContext() + mockkStatic(UUID::class) + every { UUID.randomUUID() } returns uuid + mockkConstructor(LegacyDeviceTokenProvider::class) + every { anyConstructed().containsDeviceToken() } returns false + + deviceTokenProvider = DeviceTokenProvider( + mockk { + every { encryptString(any()) } returnsArgument 0 + every { decryptString(any()) } returnsArgument 0 + } + ) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `return a random token on initialization`() = runTest { + val token = deviceTokenProvider.getDeviceToken() + assertThat(token).isEqualTo(deviceToken) + } + + @Test + fun `return the same token on repeated calls to DeviceTokenProvider`() = runTest { + val token1 = deviceTokenProvider.getDeviceToken() + every { UUID.randomUUID() } throws IllegalStateException("UUID.randomUUID called a second time") + val token2 = deviceTokenProvider.getDeviceToken() + assertThat(token1).isEqualTo(token2) + assertThat(token1).isEqualTo(deviceToken) + } + + @Test + fun `return device token from LegacyDeviceTokenProvider instead of creating a new token`() = runTest { + val legacyDeviceToken = "legacyDeviceToken" + every { anyConstructed().containsDeviceToken() } returns true + every { anyConstructed().deviceToken } returns legacyDeviceToken + + assertThat(deviceTokenProvider.getDeviceToken()).isEqualTo(legacyDeviceToken) + } + + @Test + fun `device token is at most 32 characters`() = runTest { + unmockkStatic(UUID::class) // Make sure we're always getting a real UUID here + val token = deviceTokenProvider.getDeviceToken() + assertThat(token.length).isAtMost(32) + assertThat(token.length).isEqualTo(32) + } +} From 95a9b84f370cef11cab583ad5b7e04f5af4dd53f Mon Sep 17 00:00:00 2001 From: Rajdeep Nanua Date: Thu, 13 Jun 2024 16:25:09 -0400 Subject: [PATCH 2/3] Fix IDX for org auth servers --- .../idx/kotlin/dto/v1/RequestMiddleware.kt | 9 +++-- .../kotlin/dto/v1/RequestMiddlewareTest.kt | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/dto/v1/RequestMiddleware.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/dto/v1/RequestMiddleware.kt index e8d497d..48f4c7b 100644 --- a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/dto/v1/RequestMiddleware.kt +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/dto/v1/RequestMiddleware.kt @@ -149,8 +149,13 @@ internal class InteractContext private constructor( ): InteractContext? { val codeChallenge = PkceGenerator.codeChallenge(codeVerifier) val endpoints = client.endpointsOrNull() ?: return null - val urlBuilder = endpoints.issuer.newBuilder() - .addPathSegments("v1/interact") + val urlBuilder = endpoints.issuer.newBuilder().apply { + if (endpoints.issuer.pathSegments.contains("oauth2")) { + addPathSegments("v1/interact") + } else { + addPathSegments("oauth2/v1/interact") + } + } val formBody = FormBody.Builder() .add("client_id", client.configuration.clientId) diff --git a/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/dto/v1/RequestMiddlewareTest.kt b/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/dto/v1/RequestMiddlewareTest.kt index 703bcda..9a57425 100644 --- a/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/dto/v1/RequestMiddlewareTest.kt +++ b/okta-idx-kotlin/src/test/java/com/okta/idx/kotlin/dto/v1/RequestMiddlewareTest.kt @@ -17,12 +17,17 @@ package com.okta.idx.kotlin.dto.v1 import com.google.common.truth.Truth.assertThat import com.okta.authfoundation.client.OAuth2Client +import com.okta.authfoundation.client.OidcConfiguration +import com.okta.authfoundation.client.OidcEndpoints import com.okta.idx.kotlin.client.InteractionCodeFlowContext import com.okta.idx.kotlin.dto.createField import com.okta.idx.kotlin.dto.createRemediation import com.okta.testing.network.NetworkRule import com.okta.testing.network.RequestMatchers.path +import io.mockk.every +import io.mockk.mockk import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.mockwebserver.SocketPolicy @@ -103,6 +108,35 @@ class RequestMiddlewareTest { assertThat(request.body?.contentType()).isEqualTo("application/x-www-form-urlencoded".toMediaType()) } + @Test + fun `InteractContext with org auth server appends oauth to path`() = runTest { + val exampleUrl = "https://example.com".toHttpUrl() + val endpoints = mockk { + every { issuer } returns exampleUrl + } + val oAuth2Client = OAuth2Client.create( + OidcConfiguration( + issuer = exampleUrl.toString(), + clientId = "test", + defaultScope = "openid email profile offline_access" + ), + endpoints + ) + + val interactContext = InteractContext.create(oAuth2Client, "test.okta.com/login", codeVerifier = "asdfasdf", state = "randomGen", nonce = "exampleNonce")!! + assertThat(interactContext.codeVerifier).isEqualTo("asdfasdf") + assertThat(interactContext.state).isEqualTo("randomGen") + assertThat(interactContext.nonce).isEqualTo("exampleNonce") + assertThat(interactContext.maxAge).isNull() + val request = interactContext.request + assertThat(request.url.toString()).endsWith("/oauth2/v1/interact") + assertThat(request.method).isEqualTo("POST") + val buffer = Buffer() + request.body?.writeTo(buffer) + assertThat(buffer.readUtf8()).isEqualTo("client_id=test&scope=openid%20email%20profile%20offline_access&code_challenge=JBP7NwmwWTnwTPLpL30Il_wllvmtC4qeqFXHv-uq6JI&code_challenge_method=S256&redirect_uri=test.okta.com%2Flogin&state=randomGen&nonce=exampleNonce") + assertThat(request.body?.contentType()).isEqualTo("application/x-www-form-urlencoded".toMediaType()) + } + @Test fun testInteractContextReturnsNullWhenNoEndpointsExist(): Unit = runBlocking { networkRule.enqueue(path(".well-known/openid-configuration")) { response -> response.socketPolicy = SocketPolicy.DISCONNECT_AT_START From 329c46cc2aa66f31061d3205870198475ec509bc Mon Sep 17 00:00:00 2001 From: Rajdeep Nanua Date: Thu, 13 Jun 2024 16:50:08 -0400 Subject: [PATCH 3/3] Make InteractionCodeFlow.client public --- okta-idx-kotlin/api/okta-idx-kotlin.api | 1 + .../main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/okta-idx-kotlin/api/okta-idx-kotlin.api b/okta-idx-kotlin/api/okta-idx-kotlin.api index a14905f..a814cba 100644 --- a/okta-idx-kotlin/api/okta-idx-kotlin.api +++ b/okta-idx-kotlin/api/okta-idx-kotlin.api @@ -35,6 +35,7 @@ public final class com/okta/idx/kotlin/client/InteractionCodeFlow { public fun (Lcom/okta/authfoundation/client/OidcConfiguration;)V public final fun evaluateRedirectUri (Landroid/net/Uri;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun exchangeInteractionCodeForTokens (Lcom/okta/idx/kotlin/dto/IdxRemediation;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getClient ()Lcom/okta/authfoundation/client/OAuth2Client; public final fun proceed (Lcom/okta/idx/kotlin/dto/IdxRemediation;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun resume (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun start (Landroid/net/Uri;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt index 91ce2d9..4dad50d 100644 --- a/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt +++ b/okta-idx-kotlin/src/main/java/com/okta/idx/kotlin/client/InteractionCodeFlow.kt @@ -41,7 +41,7 @@ import com.okta.idx.kotlin.dto.v1.Response as V1Response /** * The InteractionCodeFlow class is used to define and initiate an authentication workflow utilizing the Okta Identity Engine. */ -class InteractionCodeFlow(private val client: OAuth2Client) { +class InteractionCodeFlow(val client: OAuth2Client) { companion object { init { SdkVersionsRegistry.register(SDK_VERSION)