Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make AuthFoundationDefaults configurable by other SDKs #323

Merged
merged 3 commits into from
Feb 5, 2025
Merged
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
8 changes: 8 additions & 0 deletions .bacon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ test_suites:
criteria: MERGE
queue_name: small
trigger: AUTO

- name: sca-scan
script_path: /root/okta/okta-mobile-kotlin/scripts/
sort_order: '1'
timeout: '200'
script_name: dependency_scan
criteria: MAINLINE
queue_name: small
30 changes: 30 additions & 0 deletions auth-foundation/api/auth-foundation.api
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,36 @@ public final class com/okta/authfoundation/BiometricExceptionDetails$OnAuthentic
public abstract interface annotation class com/okta/authfoundation/InternalAuthFoundationApi : java/lang/annotation/Annotation {
}

public final class com/okta/authfoundation/SdkDefaults {
public static final field INSTANCE Lcom/okta/authfoundation/SdkDefaults;
public final fun getGetAccessTokenValidator ()Lkotlin/jvm/functions/Function0;
public final fun getGetCacheFactory ()Lkotlin/jvm/functions/Function0;
public final fun getGetClock ()Lkotlin/jvm/functions/Function0;
public final fun getGetComputeDispatcher ()Lkotlin/jvm/functions/Function0;
public final fun getGetCookieJar ()Lkotlin/jvm/functions/Function0;
public final fun getGetDeviceSecretValidator ()Lkotlin/jvm/functions/Function0;
public final fun getGetEventCoordinator ()Lkotlin/jvm/functions/Function0;
public final fun getGetIdTokenValidator ()Lkotlin/jvm/functions/Function0;
public final fun getGetIoDispatcher ()Lkotlin/jvm/functions/Function0;
public final fun getGetLoginCancellationDebounceTime ()Lkotlin/jvm/functions/Function0;
public final fun getGetOkHttpClientFactory ()Lkotlin/jvm/functions/Function0;
public final fun getGetTokenEncryptionHandler ()Lkotlin/jvm/functions/Function0;
public final fun getGetTokenStorageFactory ()Lkotlin/jvm/functions/Function0;
public final fun setGetAccessTokenValidator (Lkotlin/jvm/functions/Function0;)V
public final fun setGetCacheFactory (Lkotlin/jvm/functions/Function0;)V
public final fun setGetClock (Lkotlin/jvm/functions/Function0;)V
public final fun setGetComputeDispatcher (Lkotlin/jvm/functions/Function0;)V
public final fun setGetCookieJar (Lkotlin/jvm/functions/Function0;)V
public final fun setGetDeviceSecretValidator (Lkotlin/jvm/functions/Function0;)V
public final fun setGetEventCoordinator (Lkotlin/jvm/functions/Function0;)V
public final fun setGetIdTokenValidator (Lkotlin/jvm/functions/Function0;)V
public final fun setGetIoDispatcher (Lkotlin/jvm/functions/Function0;)V
public final fun setGetLoginCancellationDebounceTime (Lkotlin/jvm/functions/Function0;)V
public final fun setGetOkHttpClientFactory (Lkotlin/jvm/functions/Function0;)V
public final fun setGetTokenEncryptionHandler (Lkotlin/jvm/functions/Function0;)V
public final fun setGetTokenStorageFactory (Lkotlin/jvm/functions/Function0;)V
}

public abstract interface class com/okta/authfoundation/claims/ClaimsProvider {
public abstract fun availableClaims ()Ljava/util/Set;
public abstract fun deserializeClaim (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
Expand Down
3 changes: 1 addition & 2 deletions auth-foundation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ apply plugin: 'binary-compatibility-validator'

def copyKotlinTemplates = tasks.register('copyKotlinTemplates', Copy) {
from("src/main/kotlinTemplates")
into("$buildDir/generated/sources/kotlinTemplates")
into("${layout.buildDirectory.get()}/generated/sources/kotlinTemplates")
expand(projectVersion: project.version)
}

Expand Down Expand Up @@ -79,7 +79,6 @@ dependencies {
implementation libs.okio.jvm
implementation libs.kotlin.serialization.okio
implementation libs.security.crypto
implementation libs.startup.runtime
implementation libs.room.runtime
implementation libs.room.ktx
implementation libs.sqlcipher.android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@
*/
package com.okta.authfoundation

import com.okta.authfoundation.SdkDefaults.getAccessTokenValidator
import com.okta.authfoundation.SdkDefaults.getCacheFactory
import com.okta.authfoundation.SdkDefaults.getClock
import com.okta.authfoundation.SdkDefaults.getComputeDispatcher
import com.okta.authfoundation.SdkDefaults.getCookieJar
import com.okta.authfoundation.SdkDefaults.getDeviceSecretValidator
import com.okta.authfoundation.SdkDefaults.getEventCoordinator
import com.okta.authfoundation.SdkDefaults.getIdTokenValidator
import com.okta.authfoundation.SdkDefaults.getIoDispatcher
import com.okta.authfoundation.SdkDefaults.getLoginCancellationDebounceTime
import com.okta.authfoundation.SdkDefaults.getOkHttpClientFactory
import com.okta.authfoundation.SdkDefaults.getTokenEncryptionHandler
import com.okta.authfoundation.SdkDefaults.getTokenStorageFactory
import com.okta.authfoundation.client.AccessTokenValidator
import com.okta.authfoundation.client.Cache
import com.okta.authfoundation.client.DefaultAccessTokenValidator
import com.okta.authfoundation.client.DefaultDeviceSecretValidator
import com.okta.authfoundation.client.DefaultIdTokenValidator
import com.okta.authfoundation.client.DeviceSecretValidator
import com.okta.authfoundation.client.IdTokenValidator
import com.okta.authfoundation.client.OidcClock
import com.okta.authfoundation.client.SharedPreferencesCache
import com.okta.authfoundation.credential.DefaultTokenEncryptionHandler
import com.okta.authfoundation.credential.RoomTokenStorage
import com.okta.authfoundation.credential.Token
import com.okta.authfoundation.credential.TokenEncryptionHandler
import com.okta.authfoundation.credential.TokenStorage
import com.okta.authfoundation.events.EventCoordinator
import kotlinx.coroutines.Dispatchers
import okhttp3.Call
import okhttp3.CookieJar
import okhttp3.OkHttpClient
import java.time.Instant
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

/**
* The defaults used in various classes throughout the rest of the SDK.
Expand All @@ -48,45 +51,45 @@ import kotlin.time.Duration.Companion.seconds
*/
object AuthFoundationDefaults {
/** The default Call.Factory. */
var okHttpClientFactory: () -> Call.Factory by NoSetAfterGetWithLazyDefaultFactory { { OkHttpClient() } }
var okHttpClientFactory: () -> Call.Factory by NoSetAfterGetWithLazyDefaultFactory { getOkHttpClientFactory() }

/** The CoroutineDispatcher which should be used for IO bound tasks. */
var ioDispatcher: CoroutineContext by NoSetAfterGetWithLazyDefaultFactory { Dispatchers.IO }
var ioDispatcher: CoroutineContext by NoSetAfterGetWithLazyDefaultFactory { getIoDispatcher() }

/** The CoroutineDispatcher which should be used for compute bound tasks. */
var computeDispatcher: CoroutineContext by NoSetAfterGetWithLazyDefaultFactory { Dispatchers.Default }
var computeDispatcher: CoroutineContext by NoSetAfterGetWithLazyDefaultFactory { getComputeDispatcher() }

/** The default EventCoordinator. */
var eventCoordinator: EventCoordinator by NoSetAfterGetWithLazyDefaultFactory { EventCoordinator(emptyList()) }
var eventCoordinator: EventCoordinator by NoSetAfterGetWithLazyDefaultFactory { getEventCoordinator() }

/** The default OidcClock. */
var clock: OidcClock by NoSetAfterGetWithLazyDefaultFactory { OidcClock { Instant.now().epochSecond } }
var clock: OidcClock by NoSetAfterGetWithLazyDefaultFactory { getClock() }

/** The default IdTokenValidator. */
var idTokenValidator: IdTokenValidator by NoSetAfterGetWithLazyDefaultFactory { DefaultIdTokenValidator() }
var idTokenValidator: IdTokenValidator by NoSetAfterGetWithLazyDefaultFactory { getIdTokenValidator() }

/** The default AccessTokenValidator. */
var accessTokenValidator: AccessTokenValidator by NoSetAfterGetWithLazyDefaultFactory { DefaultAccessTokenValidator() }
var accessTokenValidator: AccessTokenValidator by NoSetAfterGetWithLazyDefaultFactory { getAccessTokenValidator() }

/** The default DeviceSecretValidator. */
var deviceSecretValidator: DeviceSecretValidator by NoSetAfterGetWithLazyDefaultFactory { DefaultDeviceSecretValidator() }
var deviceSecretValidator: DeviceSecretValidator by NoSetAfterGetWithLazyDefaultFactory { getDeviceSecretValidator() }

/** The default function that returns a singleton instance of [Cache]. */
var cacheFactory: suspend () -> Cache by NoSetAfterGetWithLazyDefaultFactory { { SharedPreferencesCache.getInstance() } }
var cacheFactory: suspend () -> Cache by NoSetAfterGetWithLazyDefaultFactory { getCacheFactory() }

/** The default function that retuns a singleton instance of [TokenStorage]. */
var tokenStorageFactory: suspend () -> TokenStorage by NoSetAfterGetWithLazyDefaultFactory { { RoomTokenStorage.getInstance() } }
var tokenStorageFactory: suspend () -> TokenStorage by NoSetAfterGetWithLazyDefaultFactory { getTokenStorageFactory() }

/** The default [TokenEncryptionHandler] for encrypting and decrypting stored [Token]s.*/
var tokenEncryptionHandler: TokenEncryptionHandler by NoSetAfterGetWithLazyDefaultFactory { DefaultTokenEncryptionHandler() }
var tokenEncryptionHandler: TokenEncryptionHandler by NoSetAfterGetWithLazyDefaultFactory { getTokenEncryptionHandler() }

/** The default [CookieJar]. By default, this is [CookieJar.NO_COOKIES]. */
var cookieJar: CookieJar by NoSetAfterGetWithLazyDefaultFactory { CookieJar.NO_COOKIES }
var cookieJar: CookieJar by NoSetAfterGetWithLazyDefaultFactory { getCookieJar() }

/** The default wait time until the web login flow is cancelled after receiving empty redirect response from the web browser.
* This can resolve some issues caused by older devices when invalid redirect results are returned from the older browser. When this is set to a non-zero value, it introduces a
* delay to all redirects when an error is received. */
var loginCancellationDebounceTime: Duration by NoSetAfterGetWithLazyDefaultFactory { 0.seconds }
var loginCancellationDebounceTime: Duration by NoSetAfterGetWithLazyDefaultFactory { getLoginCancellationDebounceTime() }

object Encryption {
/** The default keyAlias for the encryption key that will be used for encrypting the stored Token objects */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2025-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

import com.okta.authfoundation.client.AccessTokenValidator
import com.okta.authfoundation.client.Cache
import com.okta.authfoundation.client.DefaultAccessTokenValidator
import com.okta.authfoundation.client.DefaultDeviceSecretValidator
import com.okta.authfoundation.client.DefaultIdTokenValidator
import com.okta.authfoundation.client.DeviceSecretValidator
import com.okta.authfoundation.client.IdTokenValidator
import com.okta.authfoundation.client.OidcClock
import com.okta.authfoundation.client.SharedPreferencesCache
import com.okta.authfoundation.credential.DefaultTokenEncryptionHandler
import com.okta.authfoundation.credential.RoomTokenStorage
import com.okta.authfoundation.credential.TokenEncryptionHandler
import com.okta.authfoundation.credential.TokenStorage
import com.okta.authfoundation.events.EventCoordinator
import kotlinx.coroutines.Dispatchers
import okhttp3.Call
import okhttp3.CookieJar
import okhttp3.OkHttpClient
import java.time.Instant
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@InternalAuthFoundationApi
object SdkDefaults {
var getOkHttpClientFactory: () -> (() -> Call.Factory) = { { OkHttpClient() } }
var getIoDispatcher: () -> CoroutineContext = { Dispatchers.IO }
var getComputeDispatcher: () -> CoroutineContext = { Dispatchers.Default }
var getEventCoordinator: () -> EventCoordinator = { EventCoordinator(emptyList()) }
var getClock: () -> OidcClock = { OidcClock { Instant.now().epochSecond } }
var getIdTokenValidator: () -> IdTokenValidator = { DefaultIdTokenValidator() }
var getAccessTokenValidator: () -> AccessTokenValidator = { DefaultAccessTokenValidator() }
var getDeviceSecretValidator: () -> DeviceSecretValidator = { DefaultDeviceSecretValidator() }
var getCacheFactory: () -> suspend () -> Cache = { { SharedPreferencesCache.getInstance() } }
var getTokenStorageFactory: () -> suspend () -> TokenStorage = { { RoomTokenStorage.getInstance() } }
var getTokenEncryptionHandler: () -> TokenEncryptionHandler = { DefaultTokenEncryptionHandler() }
var getCookieJar: () -> CookieJar = { CookieJar.NO_COOKIES }
var getLoginCancellationDebounceTime: () -> Duration = { 0.seconds }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2025-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

import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.unmockkAll
import okhttp3.CookieJar
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.not
import org.hamcrest.MatcherAssert.assertThat
import org.junit.After
import org.junit.Before
import kotlin.test.Test

class AuthFoundationDefaultsTest {
private lateinit var defaultSdkState: DefaultSdkState

@MockK
private lateinit var sdkDefaultCookieJar: CookieJar

private lateinit var cookieJarDelegate: NoSetAfterGetWithLazyDefaultFactory<CookieJar>

@Before
fun setup() {
MockKAnnotations.init(this)
defaultSdkState = DefaultSdkState(SdkDefaults.getCookieJar)
SdkDefaults.getCookieJar = { sdkDefaultCookieJar }

cookieJarDelegate = NoSetAfterGetWithLazyDefaultFactory { SdkDefaults.getCookieJar() }
}

@After
fun tearDown() {
unmockkAll()
with(defaultSdkState) {
SdkDefaults.getCookieJar = getCookieJar
}
}

@Test
fun `get default cookie jar`() {
val cookieJar by cookieJarDelegate
assertThat(cookieJar, `is`(sdkDefaultCookieJar))
}

@Test
fun `set different sdkDefinedCookieJar, expect new value`() {
val oldCookieJar = sdkDefaultCookieJar
val newCookieJar = CookieJar.NO_COOKIES

sdkDefaultCookieJar = newCookieJar
val cookieJar by cookieJarDelegate
assertThat(cookieJar, `is`(not(oldCookieJar)))
assertThat(cookieJar, `is`(newCookieJar))
}

@Test
fun `set different sdkDefinedCookieJar after accessing it, expect old value`() {
val oldCookieJar = sdkDefaultCookieJar
val newCookieJar = CookieJar.NO_COOKIES

val cookieJar by cookieJarDelegate
assertThat(cookieJar, `is`(oldCookieJar))
assertThat(cookieJar, `is`(not(newCookieJar)))

sdkDefaultCookieJar = newCookieJar

val cookieJar2 by cookieJarDelegate
assertThat(cookieJar2, `is`(oldCookieJar))
assertThat(cookieJar2, `is`(not(newCookieJar)))
}

private class DefaultSdkState(val getCookieJar: () -> CookieJar)
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ class DefaultIdTokenValidatorTest {
@Test fun testMalformedPayload() {
val idTokenClaims = IdTokenClaims(issuer = null)
assertFailsWithMessage(
"Unexpected 'null' literal when non-nullable string was expected",
"Expected string value for a non-null key 'iss', got null literal instead at element: \$.iss\n" +
"JSON input: .....ldsHoJsPzQ\",\"ds_hash\":\"DAeLOFRqifysbgsrbOgbog\",\"nonce\":null}",
"",
idTokenClaims
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ class NetworkingTest {
val response = oktaRule.createOAuth2Client().performRequest(request)

assertThat(events.size).isEqualTo(4)
val lastRetryEvent = events.removeLast()
val lastRetryEvent = events.removeAt(events.lastIndex)
events.forEach { event ->
val exception = assertFailsWith<IllegalStateException> {
event.response.peekBody(Long.MAX_VALUE).string()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class SdkVersionsRegistryTest {
private val defaultAndroidVersion = 19
private val defaultAndroidVersion = 21

@Before fun reset() {
SdkVersionsRegistry.reset()
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ buildscript {
}

plugins {
id 'com.google.devtools.ksp' version '2.0.0-1.0.22' apply false
id 'com.google.devtools.ksp' version '2.0.21-1.0.28' apply false
}

apply plugin: 'org.jetbrains.dokka'
Expand Down
Loading