From 97ddb8b546c9d9f1807314eff2928cc0ca6afd92 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Mon, 13 Jan 2025 14:54:49 -0500 Subject: [PATCH] fix(auth): Fallback to in-memory key value storage if encryption fails (#2969) --- .../analytics/pinpoint/PinpointManager.kt | 6 +- .../data/AWSCognitoAuthCredentialStore.kt | 3 +- .../cognito/data/KeyValueRepositoryFactory.kt | 11 +- .../data/AWSCognitoAuthCredentialStoreTest.kt | 35 ++--- .../AWSCognitoLegacyCredentialStoreTest.kt | 11 +- .../core/AWSCognitoIdentityPoolOperations.kt | 2 +- .../plugins/core/data/AuthCredentialStore.kt | 12 +- .../db/CloudWatchLoggingDatabase.kt | 10 +- .../AWSPinpointPushNotificationsPlugin.kt | 4 +- .../core/store/AmplifyKeyValueRepository.kt | 46 ++++++ .../core/store/EncryptedKeyValueRepository.kt | 2 +- .../InMemoryKeyValueRepositoryProvider.kt | 26 ++++ .../store/AmplifyKeyValueRepositoryTest.kt | 139 ++++++++++++++++++ .../InMemoryKeyValueRepositoryProviderTest.kt | 48 ++++++ 14 files changed, 299 insertions(+), 56 deletions(-) create mode 100644 core/src/main/java/com/amplifyframework/core/store/AmplifyKeyValueRepository.kt create mode 100644 core/src/main/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProvider.kt create mode 100644 core/src/test/java/com/amplifyframework/core/store/AmplifyKeyValueRepositoryTest.kt create mode 100644 core/src/test/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProviderTest.kt diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt index a5533b618f..7a6d8d8cc7 100644 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt +++ b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt @@ -17,7 +17,7 @@ package com.amplifyframework.analytics.pinpoint import android.content.Context import aws.sdk.kotlin.services.pinpoint.PinpointClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import com.amplifyframework.core.store.EncryptedKeyValueRepository +import com.amplifyframework.core.store.AmplifyKeyValueRepository import com.amplifyframework.pinpoint.core.AnalyticsClient import com.amplifyframework.pinpoint.core.TargetingClient import com.amplifyframework.pinpoint.core.data.AndroidAppDetails @@ -62,7 +62,7 @@ internal class PinpointManager constructor( Context.MODE_PRIVATE ) - val encryptedStore = EncryptedKeyValueRepository( + val amplifyStore = AmplifyKeyValueRepository( context, "${awsPinpointConfiguration.appId}$PINPOINT_SHARED_PREFS_SUFFIX" ) @@ -72,7 +72,7 @@ internal class PinpointManager constructor( targetingClient = TargetingClient( context, pinpointClient, - encryptedStore, + amplifyStore, sharedPrefs, androidAppDetails, androidDeviceDetails diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStore.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStore.kt index 3f1f60a6f7..46c90bbaef 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStore.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStore.kt @@ -27,7 +27,6 @@ import kotlinx.serialization.json.Json internal class AWSCognitoAuthCredentialStore( val context: Context, private val authConfiguration: AuthConfiguration, - isPersistenceEnabled: Boolean = true, keyValueRepoFactory: KeyValueRepositoryFactory = KeyValueRepositoryFactory() ) : AuthCredentialStore { @@ -39,7 +38,7 @@ internal class AWSCognitoAuthCredentialStore( } private var keyValue: KeyValueRepository = - keyValueRepoFactory.create(context, awsKeyValueStoreIdentifier, isPersistenceEnabled) + keyValueRepoFactory.create(context, awsKeyValueStoreIdentifier) //region Save Credentials override fun saveCredential(credential: AmplifyCredential) = keyValue.put( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/KeyValueRepositoryFactory.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/KeyValueRepositoryFactory.kt index b9ec913c50..9adc59207d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/KeyValueRepositoryFactory.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/KeyValueRepositoryFactory.kt @@ -21,22 +21,21 @@ import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Co import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.APP_TOKENS_INFO_CACHE import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.AWS_MOBILE_CLIENT_PROVIDER -import com.amplifyframework.core.store.EncryptedKeyValueRepository +import com.amplifyframework.core.store.AmplifyKeyValueRepository import com.amplifyframework.core.store.InMemoryKeyValueRepository import com.amplifyframework.core.store.KeyValueRepository internal class KeyValueRepositoryFactory { - fun create(context: Context, keyValueRepoID: String, persistenceEnabled: Boolean = true): KeyValueRepository { + fun create(context: Context, keyValueRepoID: String): KeyValueRepository { return when { - keyValueRepoID == awsKeyValueStoreIdentifier -> when { - persistenceEnabled -> EncryptedKeyValueRepository(context, keyValueRepoID) - else -> InMemoryKeyValueRepository() - } + keyValueRepoID == awsKeyValueStoreIdentifier -> AmplifyKeyValueRepository(context, keyValueRepoID) + keyValueRepoID == AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER || keyValueRepoID == APP_TOKENS_INFO_CACHE || keyValueRepoID == AWS_MOBILE_CLIENT_PROVIDER || keyValueRepoID.startsWith(APP_DEVICE_INFO_CACHE) -> LegacyKeyValueRepository(context, keyValueRepoID) + else -> InMemoryKeyValueRepository() } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStoreTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStoreTest.kt index d1d6e19ba6..5c3d5c9c41 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStoreTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStoreTest.kt @@ -33,13 +33,13 @@ import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnitRunner +import org.robolectric.RobolectricTestRunner -@RunWith(MockitoJUnitRunner::class) +@RunWith(RobolectricTestRunner::class) class AWSCognitoAuthCredentialStoreTest { companion object { @@ -52,17 +52,13 @@ class AWSCognitoAuthCredentialStoreTest { private val keyValueRepoID: String = "com.amplify.credentialStore" - @Mock - private lateinit var mockConfig: AuthConfiguration + private val mockConfig = mock(AuthConfiguration::class.java) - @Mock - private lateinit var mockContext: Context + private val mockContext = mock(Context::class.java) - @Mock - private lateinit var mockKeyValue: KeyValueRepository + private val mockKeyValue: KeyValueRepository = mock(KeyValueRepository::class.java) - @Mock - private lateinit var mockFactory: KeyValueRepositoryFactory + private val mockFactory = mock(KeyValueRepositoryFactory::class.java) private lateinit var persistentStore: AWSCognitoAuthCredentialStore @@ -71,8 +67,7 @@ class AWSCognitoAuthCredentialStoreTest { Mockito.`when`( mockFactory.create( mockContext, - keyValueRepoID, - true + keyValueRepoID ) ).thenReturn(mockKeyValue) @@ -84,7 +79,7 @@ class AWSCognitoAuthCredentialStoreTest { @Test fun testSaveCredentialWithUserPool() { setupUserPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) persistentStore.saveCredential(getCredential()) verify(mockKeyValue, times(1)) .put(KEY_WITH_USER_POOL, serialized(getCredential())) @@ -93,7 +88,7 @@ class AWSCognitoAuthCredentialStoreTest { @Test fun testSaveCredentialWithIdentityPool() { setupIdentityPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) persistentStore.saveCredential(getCredential()) @@ -105,7 +100,7 @@ class AWSCognitoAuthCredentialStoreTest { fun testSaveCredentialWithUserAndIdentityPool() { setupUserPoolConfig() setupIdentityPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) persistentStore.saveCredential(getCredential()) @@ -117,7 +112,7 @@ class AWSCognitoAuthCredentialStoreTest { fun testRetrieveCredential() { setupUserPoolConfig() setupIdentityPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) val actual = persistentStore.retrieveCredential() @@ -127,7 +122,7 @@ class AWSCognitoAuthCredentialStoreTest { @Test fun testDeleteCredential() { setupUserPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) persistentStore.deleteCredential() @@ -136,7 +131,7 @@ class AWSCognitoAuthCredentialStoreTest { @Test fun testInMemoryCredentialStore() { - val store = AWSCognitoAuthCredentialStore(mockContext, mockConfig, false) + val store = AWSCognitoAuthCredentialStore(mockContext, mockConfig) store.saveCredential(getCredential()) assertEquals(getCredential(), store.retrieveCredential()) @@ -150,7 +145,7 @@ class AWSCognitoAuthCredentialStoreTest { setupUserPoolConfig() setupIdentityPoolConfig() - persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory) + persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory) } private fun setupIdentityPoolConfig() { diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt index efff77b4fe..549ad27717 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt @@ -117,28 +117,25 @@ class AWSCognitoLegacyCredentialStoreTest { `when`( mockFactory.create( mockContext, - AWSCognitoLegacyCredentialStore.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER, - true + AWSCognitoLegacyCredentialStore.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER ) ).thenReturn(mockKeyValue) `when`( mockFactory.create( mockContext, - AWSCognitoLegacyCredentialStore.APP_TOKENS_INFO_CACHE, - true + AWSCognitoLegacyCredentialStore.APP_TOKENS_INFO_CACHE ) ).thenReturn(mockKeyValue) `when`( mockFactory.create( mockContext, - AWSCognitoLegacyCredentialStore.AWS_MOBILE_CLIENT_PROVIDER, - true + AWSCognitoLegacyCredentialStore.AWS_MOBILE_CLIENT_PROVIDER ) ).thenReturn(mockKeyValue) - `when`(mockFactory.create(mockContext, deviceDetailsCacheKey, true)).thenReturn(mockKeyValue) + `when`(mockFactory.create(mockContext, deviceDetailsCacheKey)).thenReturn(mockKeyValue) } @Test diff --git a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/AWSCognitoIdentityPoolOperations.kt b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/AWSCognitoIdentityPoolOperations.kt index e2f4281ddc..50df75ced5 100644 --- a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/AWSCognitoIdentityPoolOperations.kt +++ b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/AWSCognitoIdentityPoolOperations.kt @@ -68,7 +68,7 @@ class AWSCognitoIdentityPoolOperations( private val KEY_LOGINS_PROVIDER = "amplify.${identityPool.poolId}.session.loginsProvider" private val KEY_IDENTITY_ID = "amplify.${identityPool.poolId}.session.identityId" private val KEY_AWS_CREDENTIALS = "amplify.${identityPool.poolId}.session.credential" - private val awsAuthCredentialStore = AuthCredentialStore(context.applicationContext, pluginKeySanitized, true) + private val awsAuthCredentialStore = AuthCredentialStore(context.applicationContext, pluginKeySanitized) val cognitoIdentityClient = CognitoClientFactory.createIdentityClient( identityPool, diff --git a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/data/AuthCredentialStore.kt b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/data/AuthCredentialStore.kt index 501eda8854..0b191dcedf 100644 --- a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/data/AuthCredentialStore.kt +++ b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/data/AuthCredentialStore.kt @@ -16,22 +16,16 @@ package com.amplifyframework.auth.plugins.core.data import android.content.Context -import com.amplifyframework.core.store.EncryptedKeyValueRepository -import com.amplifyframework.core.store.InMemoryKeyValueRepository +import com.amplifyframework.core.store.AmplifyKeyValueRepository import com.amplifyframework.core.store.KeyValueRepository internal class AuthCredentialStore( context: Context, - keyValueStoreIdentifierSuffix: String, - isPersistenceEnabled: Boolean + keyValueStoreIdentifierSuffix: String ) { private val keyValueStoreIdentifier = "com.amplify.credentialStore.$keyValueStoreIdentifierSuffix" - private val keyValue: KeyValueRepository = if (isPersistenceEnabled) { - EncryptedKeyValueRepository(context, keyValueStoreIdentifier) - } else { - InMemoryKeyValueRepository() - } + private val keyValue: KeyValueRepository = AmplifyKeyValueRepository(context, keyValueStoreIdentifier) fun put(key: String, value: String) = keyValue.put(key, value) fun get(key: String): String? = keyValue.get(key) diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/db/CloudWatchLoggingDatabase.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/db/CloudWatchLoggingDatabase.kt index bcc5f13a24..2d87e59f28 100644 --- a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/db/CloudWatchLoggingDatabase.kt +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/db/CloudWatchLoggingDatabase.kt @@ -20,7 +20,7 @@ import android.content.UriMatcher import android.database.Cursor import android.net.Uri import androidx.annotation.VisibleForTesting -import com.amplifyframework.core.store.EncryptedKeyValueRepository +import com.amplifyframework.core.store.AmplifyKeyValueRepository import com.amplifyframework.logging.cloudwatch.models.CloudWatchLogEvent import java.util.UUID import kotlinx.coroutines.CoroutineDispatcher @@ -36,8 +36,8 @@ internal class CloudWatchLoggingDatabase( private val logEventsId = 20 private val passphraseKey = "passphrase" private val mb = 1024 * 1024 - private val encryptedKeyValueRepository: EncryptedKeyValueRepository by lazy { - EncryptedKeyValueRepository( + private val amplifyKeyValueRepository: AmplifyKeyValueRepository by lazy { + AmplifyKeyValueRepository( context, "awscloudwatchloggingdb" ) @@ -137,7 +137,7 @@ internal class CloudWatchLoggingDatabase( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun getDatabasePassphrase(): String { - return encryptedKeyValueRepository.get(passphraseKey) ?: kotlin.run { + return amplifyKeyValueRepository.get(passphraseKey) ?: kotlin.run { val passphrase = UUID.randomUUID().toString() // If the database is restored from backup and the passphrase key is not present, // this would result in the database file not getting loaded. @@ -146,7 +146,7 @@ internal class CloudWatchLoggingDatabase( if (path.exists()) { path.delete() } - encryptedKeyValueRepository.put(passphraseKey, passphrase) + amplifyKeyValueRepository.put(passphraseKey, passphrase) passphrase } } diff --git a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt index 9b4cb61bb4..9e0be9fb9f 100644 --- a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt +++ b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt @@ -32,7 +32,7 @@ import com.amplifyframework.core.Amplify import com.amplifyframework.core.Consumer import com.amplifyframework.core.category.CategoryType import com.amplifyframework.core.configuration.AmplifyOutputsData -import com.amplifyframework.core.store.EncryptedKeyValueRepository +import com.amplifyframework.core.store.AmplifyKeyValueRepository import com.amplifyframework.core.store.KeyValueRepository import com.amplifyframework.notifications.pushnotifications.NotificationPayload import com.amplifyframework.notifications.pushnotifications.PushNotificationResult @@ -120,7 +120,7 @@ class AWSPinpointPushNotificationsPlugin : PushNotificationsPlugin File(dir, fileName) } ) - private val sharedPreferences by lazy { getOrCreateSharedPreferences() } + internal val sharedPreferences by lazy { getOrCreateSharedPreferences() } override fun put(dataKey: String, value: String?) = edit { putString(dataKey, value) } override fun get(dataKey: String): String? = sharedPreferences.getString(dataKey, null) diff --git a/core/src/main/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProvider.kt b/core/src/main/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProvider.kt new file mode 100644 index 0000000000..2f95eb38a1 --- /dev/null +++ b/core/src/main/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.core.store + +import java.util.concurrent.ConcurrentHashMap + +internal object InMemoryKeyValueRepositoryProvider { + private val inMemoryRepositories = ConcurrentHashMap() + + @Synchronized + fun getKeyValueRepository(name: String): InMemoryKeyValueRepository { + return inMemoryRepositories.getOrPut(name) { InMemoryKeyValueRepository() } + } +} diff --git a/core/src/test/java/com/amplifyframework/core/store/AmplifyKeyValueRepositoryTest.kt b/core/src/test/java/com/amplifyframework/core/store/AmplifyKeyValueRepositoryTest.kt new file mode 100644 index 0000000000..e0640437b2 --- /dev/null +++ b/core/src/test/java/com/amplifyframework/core/store/AmplifyKeyValueRepositoryTest.kt @@ -0,0 +1,139 @@ +package com.amplifyframework.core.store + +import android.security.keystore.KeyProperties +import androidx.test.platform.app.InstrumentationRegistry +import io.kotest.matchers.shouldBe +import java.io.InputStream +import java.io.OutputStream +import java.security.Key +import java.security.KeyStore +import java.security.KeyStoreSpi +import java.security.Provider +import java.security.Security +import java.security.cert.Certificate +import java.util.Collections +import java.util.Date +import java.util.Enumeration +import java.util.UUID +import javax.crypto.KeyGenerator +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class AmplifyKeyValueRepositoryTest { + + companion object { + private val testKey = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES).generateKey() + } + + private val context = InstrumentationRegistry.getInstrumentation().context + + @After + fun tearDown() { + Security.removeProvider("AndroidKeyStore") + } + + @Test + fun `get encrypted repository succeeds`() { + Security.addProvider(FakeAndroidKeyStoreProvider(GoodFakeKeyStore::class.java.name)) + val expectedPrefsName = UUID.randomUUID().toString() + val expectedKey = "k1" + val expectedValue = "v1" + + AmplifyKeyValueRepository(context, expectedPrefsName).apply { + put(expectedKey, expectedValue) + } + + EncryptedKeyValueRepository(context, expectedPrefsName).get(expectedKey) shouldBe expectedValue + } + + @Test + fun `get in memory repository fallback succeeds`() { + Security.addProvider(FakeAndroidKeyStoreProvider(BadFakeKeyStore::class.java.name)) + val expectedPrefsName = UUID.randomUUID().toString() + val expectedKey = "k1" + val expectedValue = "v1" + + AmplifyKeyValueRepository(context, expectedPrefsName).apply { + put(expectedKey, expectedValue) + } + + InMemoryKeyValueRepositoryProvider + .getKeyValueRepository(expectedPrefsName) + .get(expectedKey) shouldBe expectedValue + } + + @Test + fun `test KeyValueRepository method passthrough`() { + Security.addProvider(FakeAndroidKeyStoreProvider(BadFakeKeyStore::class.java.name)) + val expectedPrefsName = UUID.randomUUID().toString() + val expectedKey = "k1" + val expectedValue = "v1" + val expectedKey2 = "k1" + val expectedValue2 = "v1" + + val repository = AmplifyKeyValueRepository(context, expectedPrefsName) + val inMemoryRepository = InMemoryKeyValueRepositoryProvider.getKeyValueRepository(expectedPrefsName) + + // validate put passes through to in memory repository + repository.put(expectedKey, expectedValue) + inMemoryRepository.get(expectedKey) shouldBe expectedValue + + // validate get reads from in memory repository + inMemoryRepository.put(expectedKey, expectedValue) + repository.get(expectedKey2) shouldBe expectedValue2 + + // validate remove passes through to in memory repository + repository.remove(expectedKey) + inMemoryRepository.get(expectedKey) shouldBe null + + // validate removeAll passes through to in memory repository + repository.removeAll() + inMemoryRepository.get(expectedKey2) shouldBe null + } + + class FakeAndroidKeyStoreProvider(androidKeyStoreClassName: String) : Provider( + "AndroidKeyStore", 1.0, "Fake AndroidKeyStore provider" + ) { + init { put("KeyStore.AndroidKeyStore", androidKeyStoreClassName) } + } + + open class FakeKeyStore : KeyStoreSpi() { + override fun engineIsKeyEntry(alias: String?) = true + override fun engineIsCertificateEntry(alias: String?) = true + override fun engineGetCertificate(alias: String?): Certificate { throw NotImplementedError() } + override fun engineGetCreationDate(alias: String?): Date { return Date() } + override fun engineDeleteEntry(alias: String?) {} + override fun engineSetKeyEntry( + alias: String?, + key: Key?, + password: CharArray?, + chain: Array? + ) {} + override fun engineGetEntry(alias: String?, protParam: KeyStore.ProtectionParameter?): KeyStore.Entry { + throw NotImplementedError() + } + override fun engineSetKeyEntry(alias: String?, key: ByteArray?, chain: Array?) {} + override fun engineStore(stream: OutputStream?, password: CharArray?) {} + override fun engineSize() = 1 + override fun engineAliases(): Enumeration = Collections.emptyEnumeration() + override fun engineContainsAlias(alias: String?) = true + override fun engineLoad(stream: InputStream?, password: CharArray?) {} + override fun engineGetCertificateChain(alias: String?) = emptyArray() + override fun engineSetCertificateEntry(alias: String?, cert: Certificate?) {} + override fun engineGetCertificateAlias(cert: Certificate?) = null + override fun engineGetKey(alias: String?, password: CharArray?): Key? { + throw NotImplementedError("No Need to implement for testing") + } + } + + class GoodFakeKeyStore : FakeKeyStore() { + override fun engineGetKey(alias: String?, password: CharArray?): Key = testKey + } + + class BadFakeKeyStore : FakeKeyStore() { + override fun engineGetKey(alias: String?, password: CharArray?): Key? = null + } +} diff --git a/core/src/test/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProviderTest.kt b/core/src/test/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProviderTest.kt new file mode 100644 index 0000000000..425df0f5a1 --- /dev/null +++ b/core/src/test/java/com/amplifyframework/core/store/InMemoryKeyValueRepositoryProviderTest.kt @@ -0,0 +1,48 @@ +package com.amplifyframework.core.store + +import io.kotest.matchers.equals.shouldBeEqual +import java.util.UUID +import org.junit.Test + +class InMemoryKeyValueRepositoryProviderTest { + + @Test + fun `test ability to create multiple in memory repositories`() { + val expectedRepo1Name = UUID.randomUUID().toString() + val expectedRepo2Name = UUID.randomUUID().toString() + val key1 = "testKey" + val expectedRepo1Key1Value = "testVal1" + val expectedRepo2Key1Value = "testVal2" + val repo1 = InMemoryKeyValueRepositoryProvider.getKeyValueRepository(expectedRepo1Name).apply { + put(key1, expectedRepo1Key1Value) + } + val repo2 = InMemoryKeyValueRepositoryProvider.getKeyValueRepository(expectedRepo2Name).apply { + put(key1, expectedRepo2Key1Value) + } + + expectedRepo1Key1Value shouldBeEqual repo1.get(key1)!! + expectedRepo2Key1Value shouldBeEqual repo2.get(key1)!! + } + + @Test + fun `test ability to get repository by name`() { + // GIVEN + val expectedRepo1Name = UUID.randomUUID().toString() + val key1 = "testKey" + val expectedRepo1Key1Value = "testVal1" + + // WHEN + val repo1 = InMemoryKeyValueRepositoryProvider.getKeyValueRepository(expectedRepo1Name).apply { + put(key1, expectedRepo1Key1Value) + } + + // THEN + expectedRepo1Key1Value shouldBeEqual repo1.get(key1)!! + + // WHEN + val repo2 = InMemoryKeyValueRepositoryProvider.getKeyValueRepository(expectedRepo1Name) + + // THEN + expectedRepo1Key1Value shouldBeEqual repo2.get(key1)!! + } +}