Skip to content

Commit 97ddb8b

Browse files
author
Tyler Roach
authored
fix(auth): Fallback to in-memory key value storage if encryption fails (#2969)
1 parent 7d1603c commit 97ddb8b

File tree

14 files changed

+299
-56
lines changed

14 files changed

+299
-56
lines changed

aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package com.amplifyframework.analytics.pinpoint
1717
import android.content.Context
1818
import aws.sdk.kotlin.services.pinpoint.PinpointClient
1919
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
20-
import com.amplifyframework.core.store.EncryptedKeyValueRepository
20+
import com.amplifyframework.core.store.AmplifyKeyValueRepository
2121
import com.amplifyframework.pinpoint.core.AnalyticsClient
2222
import com.amplifyframework.pinpoint.core.TargetingClient
2323
import com.amplifyframework.pinpoint.core.data.AndroidAppDetails
@@ -62,7 +62,7 @@ internal class PinpointManager constructor(
6262
Context.MODE_PRIVATE
6363
)
6464

65-
val encryptedStore = EncryptedKeyValueRepository(
65+
val amplifyStore = AmplifyKeyValueRepository(
6666
context,
6767
"${awsPinpointConfiguration.appId}$PINPOINT_SHARED_PREFS_SUFFIX"
6868
)
@@ -72,7 +72,7 @@ internal class PinpointManager constructor(
7272
targetingClient = TargetingClient(
7373
context,
7474
pinpointClient,
75-
encryptedStore,
75+
amplifyStore,
7676
sharedPrefs,
7777
androidAppDetails,
7878
androidDeviceDetails

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStore.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import kotlinx.serialization.json.Json
2727
internal class AWSCognitoAuthCredentialStore(
2828
val context: Context,
2929
private val authConfiguration: AuthConfiguration,
30-
isPersistenceEnabled: Boolean = true,
3130
keyValueRepoFactory: KeyValueRepositoryFactory = KeyValueRepositoryFactory()
3231
) : AuthCredentialStore {
3332

@@ -39,7 +38,7 @@ internal class AWSCognitoAuthCredentialStore(
3938
}
4039

4140
private var keyValue: KeyValueRepository =
42-
keyValueRepoFactory.create(context, awsKeyValueStoreIdentifier, isPersistenceEnabled)
41+
keyValueRepoFactory.create(context, awsKeyValueStoreIdentifier)
4342

4443
//region Save Credentials
4544
override fun saveCredential(credential: AmplifyCredential) = keyValue.put(

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/KeyValueRepositoryFactory.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,21 @@ import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Co
2121
import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.APP_TOKENS_INFO_CACHE
2222
import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER
2323
import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore.Companion.AWS_MOBILE_CLIENT_PROVIDER
24-
import com.amplifyframework.core.store.EncryptedKeyValueRepository
24+
import com.amplifyframework.core.store.AmplifyKeyValueRepository
2525
import com.amplifyframework.core.store.InMemoryKeyValueRepository
2626
import com.amplifyframework.core.store.KeyValueRepository
2727

2828
internal class KeyValueRepositoryFactory {
29-
fun create(context: Context, keyValueRepoID: String, persistenceEnabled: Boolean = true): KeyValueRepository {
29+
fun create(context: Context, keyValueRepoID: String): KeyValueRepository {
3030
return when {
31-
keyValueRepoID == awsKeyValueStoreIdentifier -> when {
32-
persistenceEnabled -> EncryptedKeyValueRepository(context, keyValueRepoID)
33-
else -> InMemoryKeyValueRepository()
34-
}
31+
keyValueRepoID == awsKeyValueStoreIdentifier -> AmplifyKeyValueRepository(context, keyValueRepoID)
32+
3533
keyValueRepoID == AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER ||
3634
keyValueRepoID == APP_TOKENS_INFO_CACHE ||
3735
keyValueRepoID == AWS_MOBILE_CLIENT_PROVIDER ||
3836
keyValueRepoID.startsWith(APP_DEVICE_INFO_CACHE) ->
3937
LegacyKeyValueRepository(context, keyValueRepoID)
38+
4039
else -> InMemoryKeyValueRepository()
4140
}
4241
}

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoAuthCredentialStoreTest.kt

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ import org.junit.Assert
3333
import org.junit.Before
3434
import org.junit.Test
3535
import org.junit.runner.RunWith
36-
import org.mockito.Mock
3736
import org.mockito.Mockito
37+
import org.mockito.Mockito.mock
3838
import org.mockito.Mockito.times
3939
import org.mockito.Mockito.verify
40-
import org.mockito.junit.MockitoJUnitRunner
40+
import org.robolectric.RobolectricTestRunner
4141

42-
@RunWith(MockitoJUnitRunner::class)
42+
@RunWith(RobolectricTestRunner::class)
4343
class AWSCognitoAuthCredentialStoreTest {
4444

4545
companion object {
@@ -52,17 +52,13 @@ class AWSCognitoAuthCredentialStoreTest {
5252

5353
private val keyValueRepoID: String = "com.amplify.credentialStore"
5454

55-
@Mock
56-
private lateinit var mockConfig: AuthConfiguration
55+
private val mockConfig = mock(AuthConfiguration::class.java)
5756

58-
@Mock
59-
private lateinit var mockContext: Context
57+
private val mockContext = mock(Context::class.java)
6058

61-
@Mock
62-
private lateinit var mockKeyValue: KeyValueRepository
59+
private val mockKeyValue: KeyValueRepository = mock(KeyValueRepository::class.java)
6360

64-
@Mock
65-
private lateinit var mockFactory: KeyValueRepositoryFactory
61+
private val mockFactory = mock(KeyValueRepositoryFactory::class.java)
6662

6763
private lateinit var persistentStore: AWSCognitoAuthCredentialStore
6864

@@ -71,8 +67,7 @@ class AWSCognitoAuthCredentialStoreTest {
7167
Mockito.`when`(
7268
mockFactory.create(
7369
mockContext,
74-
keyValueRepoID,
75-
true
70+
keyValueRepoID
7671
)
7772
).thenReturn(mockKeyValue)
7873

@@ -84,7 +79,7 @@ class AWSCognitoAuthCredentialStoreTest {
8479
@Test
8580
fun testSaveCredentialWithUserPool() {
8681
setupUserPoolConfig()
87-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
82+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
8883
persistentStore.saveCredential(getCredential())
8984
verify(mockKeyValue, times(1))
9085
.put(KEY_WITH_USER_POOL, serialized(getCredential()))
@@ -93,7 +88,7 @@ class AWSCognitoAuthCredentialStoreTest {
9388
@Test
9489
fun testSaveCredentialWithIdentityPool() {
9590
setupIdentityPoolConfig()
96-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
91+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
9792

9893
persistentStore.saveCredential(getCredential())
9994

@@ -105,7 +100,7 @@ class AWSCognitoAuthCredentialStoreTest {
105100
fun testSaveCredentialWithUserAndIdentityPool() {
106101
setupUserPoolConfig()
107102
setupIdentityPoolConfig()
108-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
103+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
109104

110105
persistentStore.saveCredential(getCredential())
111106

@@ -117,7 +112,7 @@ class AWSCognitoAuthCredentialStoreTest {
117112
fun testRetrieveCredential() {
118113
setupUserPoolConfig()
119114
setupIdentityPoolConfig()
120-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
115+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
121116

122117
val actual = persistentStore.retrieveCredential()
123118

@@ -127,7 +122,7 @@ class AWSCognitoAuthCredentialStoreTest {
127122
@Test
128123
fun testDeleteCredential() {
129124
setupUserPoolConfig()
130-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
125+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
131126

132127
persistentStore.deleteCredential()
133128

@@ -136,7 +131,7 @@ class AWSCognitoAuthCredentialStoreTest {
136131

137132
@Test
138133
fun testInMemoryCredentialStore() {
139-
val store = AWSCognitoAuthCredentialStore(mockContext, mockConfig, false)
134+
val store = AWSCognitoAuthCredentialStore(mockContext, mockConfig)
140135

141136
store.saveCredential(getCredential())
142137
assertEquals(getCredential(), store.retrieveCredential())
@@ -150,7 +145,7 @@ class AWSCognitoAuthCredentialStoreTest {
150145

151146
setupUserPoolConfig()
152147
setupIdentityPoolConfig()
153-
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, true, mockFactory)
148+
persistentStore = AWSCognitoAuthCredentialStore(mockContext, mockConfig, mockFactory)
154149
}
155150

156151
private fun setupIdentityPoolConfig() {

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,25 @@ class AWSCognitoLegacyCredentialStoreTest {
117117
`when`(
118118
mockFactory.create(
119119
mockContext,
120-
AWSCognitoLegacyCredentialStore.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER,
121-
true
120+
AWSCognitoLegacyCredentialStore.AWS_KEY_VALUE_STORE_NAMESPACE_IDENTIFIER
122121
)
123122
).thenReturn(mockKeyValue)
124123

125124
`when`(
126125
mockFactory.create(
127126
mockContext,
128-
AWSCognitoLegacyCredentialStore.APP_TOKENS_INFO_CACHE,
129-
true
127+
AWSCognitoLegacyCredentialStore.APP_TOKENS_INFO_CACHE
130128
)
131129
).thenReturn(mockKeyValue)
132130

133131
`when`(
134132
mockFactory.create(
135133
mockContext,
136-
AWSCognitoLegacyCredentialStore.AWS_MOBILE_CLIENT_PROVIDER,
137-
true
134+
AWSCognitoLegacyCredentialStore.AWS_MOBILE_CLIENT_PROVIDER
138135
)
139136
).thenReturn(mockKeyValue)
140137

141-
`when`(mockFactory.create(mockContext, deviceDetailsCacheKey, true)).thenReturn(mockKeyValue)
138+
`when`(mockFactory.create(mockContext, deviceDetailsCacheKey)).thenReturn(mockKeyValue)
142139
}
143140

144141
@Test

aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/AWSCognitoIdentityPoolOperations.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class AWSCognitoIdentityPoolOperations(
6868
private val KEY_LOGINS_PROVIDER = "amplify.${identityPool.poolId}.session.loginsProvider"
6969
private val KEY_IDENTITY_ID = "amplify.${identityPool.poolId}.session.identityId"
7070
private val KEY_AWS_CREDENTIALS = "amplify.${identityPool.poolId}.session.credential"
71-
private val awsAuthCredentialStore = AuthCredentialStore(context.applicationContext, pluginKeySanitized, true)
71+
private val awsAuthCredentialStore = AuthCredentialStore(context.applicationContext, pluginKeySanitized)
7272

7373
val cognitoIdentityClient = CognitoClientFactory.createIdentityClient(
7474
identityPool,

aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/data/AuthCredentialStore.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,16 @@
1616
package com.amplifyframework.auth.plugins.core.data
1717

1818
import android.content.Context
19-
import com.amplifyframework.core.store.EncryptedKeyValueRepository
20-
import com.amplifyframework.core.store.InMemoryKeyValueRepository
19+
import com.amplifyframework.core.store.AmplifyKeyValueRepository
2120
import com.amplifyframework.core.store.KeyValueRepository
2221

2322
internal class AuthCredentialStore(
2423
context: Context,
25-
keyValueStoreIdentifierSuffix: String,
26-
isPersistenceEnabled: Boolean
24+
keyValueStoreIdentifierSuffix: String
2725
) {
2826
private val keyValueStoreIdentifier = "com.amplify.credentialStore.$keyValueStoreIdentifierSuffix"
2927

30-
private val keyValue: KeyValueRepository = if (isPersistenceEnabled) {
31-
EncryptedKeyValueRepository(context, keyValueStoreIdentifier)
32-
} else {
33-
InMemoryKeyValueRepository()
34-
}
28+
private val keyValue: KeyValueRepository = AmplifyKeyValueRepository(context, keyValueStoreIdentifier)
3529

3630
fun put(key: String, value: String) = keyValue.put(key, value)
3731
fun get(key: String): String? = keyValue.get(key)

aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/db/CloudWatchLoggingDatabase.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import android.content.UriMatcher
2020
import android.database.Cursor
2121
import android.net.Uri
2222
import androidx.annotation.VisibleForTesting
23-
import com.amplifyframework.core.store.EncryptedKeyValueRepository
23+
import com.amplifyframework.core.store.AmplifyKeyValueRepository
2424
import com.amplifyframework.logging.cloudwatch.models.CloudWatchLogEvent
2525
import java.util.UUID
2626
import kotlinx.coroutines.CoroutineDispatcher
@@ -36,8 +36,8 @@ internal class CloudWatchLoggingDatabase(
3636
private val logEventsId = 20
3737
private val passphraseKey = "passphrase"
3838
private val mb = 1024 * 1024
39-
private val encryptedKeyValueRepository: EncryptedKeyValueRepository by lazy {
40-
EncryptedKeyValueRepository(
39+
private val amplifyKeyValueRepository: AmplifyKeyValueRepository by lazy {
40+
AmplifyKeyValueRepository(
4141
context,
4242
"awscloudwatchloggingdb"
4343
)
@@ -137,7 +137,7 @@ internal class CloudWatchLoggingDatabase(
137137

138138
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
139139
fun getDatabasePassphrase(): String {
140-
return encryptedKeyValueRepository.get(passphraseKey) ?: kotlin.run {
140+
return amplifyKeyValueRepository.get(passphraseKey) ?: kotlin.run {
141141
val passphrase = UUID.randomUUID().toString()
142142
// If the database is restored from backup and the passphrase key is not present,
143143
// this would result in the database file not getting loaded.
@@ -146,7 +146,7 @@ internal class CloudWatchLoggingDatabase(
146146
if (path.exists()) {
147147
path.delete()
148148
}
149-
encryptedKeyValueRepository.put(passphraseKey, passphrase)
149+
amplifyKeyValueRepository.put(passphraseKey, passphrase)
150150
passphrase
151151
}
152152
}

aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import com.amplifyframework.core.Amplify
3232
import com.amplifyframework.core.Consumer
3333
import com.amplifyframework.core.category.CategoryType
3434
import com.amplifyframework.core.configuration.AmplifyOutputsData
35-
import com.amplifyframework.core.store.EncryptedKeyValueRepository
35+
import com.amplifyframework.core.store.AmplifyKeyValueRepository
3636
import com.amplifyframework.core.store.KeyValueRepository
3737
import com.amplifyframework.notifications.pushnotifications.NotificationPayload
3838
import com.amplifyframework.notifications.pushnotifications.PushNotificationResult
@@ -120,7 +120,7 @@ class AWSPinpointPushNotificationsPlugin : PushNotificationsPlugin<PinpointClien
120120
configuration.appId + AWS_PINPOINT_PUSHNOTIFICATIONS_PREFERENCES_SUFFIX,
121121
Context.MODE_PRIVATE
122122
)
123-
store = EncryptedKeyValueRepository(
123+
store = AmplifyKeyValueRepository(
124124
context,
125125
configuration.appId + AWS_PINPOINT_PUSHNOTIFICATIONS_PREFERENCES_SUFFIX
126126
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amplifyframework.core.store
16+
17+
import android.content.Context
18+
import com.amplifyframework.annotations.InternalAmplifyApi
19+
20+
@InternalAmplifyApi
21+
class AmplifyKeyValueRepository(
22+
private val context: Context,
23+
private val sharedPreferencesName: String
24+
) : KeyValueRepository {
25+
26+
// We attempt to get an encrypted persistent repository, but if that fails, use an in memory one instead.
27+
private val repository: KeyValueRepository by lazy {
28+
try {
29+
EncryptedKeyValueRepository(context, sharedPreferencesName).also {
30+
// This attempts to open EncryptedSharedPrefs. If it opens, we are good to use.
31+
it.sharedPreferences
32+
}
33+
} catch (exception: Exception) {
34+
// We crashed attempting to open EncryptedKeyValueRepository, use In-Memory Instead.
35+
InMemoryKeyValueRepositoryProvider.getKeyValueRepository(sharedPreferencesName)
36+
}
37+
}
38+
39+
override fun get(dataKey: String): String? = repository.get(dataKey)
40+
41+
override fun put(dataKey: String, value: String?) = repository.put(dataKey, value)
42+
43+
override fun remove(dataKey: String) = repository.remove(dataKey)
44+
45+
override fun removeAll() = repository.removeAll()
46+
}

0 commit comments

Comments
 (0)