Skip to content

Commit

Permalink
[MOB-9235] Use shared prefs and try to migrate date from encrypted to…
Browse files Browse the repository at this point in the history
… shared prefs
  • Loading branch information
sumeruchat committed Dec 18, 2024
1 parent 8fdec83 commit 284fe2e
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 100 deletions.
109 changes: 9 additions & 100 deletions iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import androidx.annotation.RequiresApi
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

import com.iterable.iterableapi.IterableKeychainEncryptedDataMigrator

class IterableKeychain {

private val TAG = "IterableKeychain"
private var sharedPrefs: SharedPreferences

private val encryptedSharedPrefsFileName = "iterable-encrypted-shared-preferences"

private val emailKey = "iterable-email"
private val userIdKey = "iterable-user-id"
private val authTokenKey = "iterable-auth-token"
Expand All @@ -22,61 +22,14 @@ class IterableKeychain {

constructor(context: Context, encryptionEnforced: Boolean) {

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
encryptionEnabled = false
sharedPrefs = context.getSharedPreferences(
IterableConstants.SHARED_PREFS_FILE,
Context.MODE_PRIVATE
)
IterableLogger.v(TAG, "SharedPreferences being used")
} else {
// See if EncryptedSharedPreferences can be created successfully
try {
val masterKeyAlias = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
sharedPrefs = EncryptedSharedPreferences.create(
context,
encryptedSharedPrefsFileName,
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptionEnabled = true
} catch (e: Throwable) {
if (e is Error) {
IterableLogger.e(
TAG,
"EncryptionSharedPreference creation failed with Error. Attempting to continue"
)
}

if (encryptionEnforced) {
//TODO: In-memory or similar solution needs to be implemented in the future.
IterableLogger.w(
TAG,
"Encryption is enforced. PII will not be persisted due to EncryptionSharedPreference failure. Email/UserId and Auth token will have to be passed for every app session.",
e
)
throw e.fillInStackTrace()
} else {
sharedPrefs = context.getSharedPreferences(
IterableConstants.SHARED_PREFS_FILE,
Context.MODE_PRIVATE
)
IterableLogger.w(
TAG,
"Using SharedPreference as EncryptionSharedPreference creation failed."
)
encryptionEnabled = false
}
}
sharedPrefs = context.getSharedPreferences(
IterableConstants.SHARED_PREFS_FILE,
Context.MODE_PRIVATE
)
IterableLogger.v(TAG, "SharedPreferences being used")

//Try to migrate data from SharedPreferences to EncryptedSharedPreferences
if (encryptionEnabled) {
migrateAuthDataFromSharedPrefsToKeychain(context)
}
}
// Attempt migration from encrypted preferences
IterableKeychainEncryptedDataMigrator(context, sharedPrefs).attemptMigration()
}

fun getEmail(): String? {
Expand Down Expand Up @@ -108,48 +61,4 @@ class IterableKeychain {
.putString(authTokenKey, authToken)
.apply()
}

@RequiresApi(api = Build.VERSION_CODES.M)
private fun migrateAuthDataFromSharedPrefsToKeychain(context: Context) {
val oldPrefs: SharedPreferences = context.getSharedPreferences(
IterableConstants.SHARED_PREFS_FILE,
Context.MODE_PRIVATE
)
val sharedPrefsEmail = oldPrefs.getString(IterableConstants.SHARED_PREFS_EMAIL_KEY, null)
val sharedPrefsUserId = oldPrefs.getString(IterableConstants.SHARED_PREFS_USERID_KEY, null)
val sharedPrefsAuthToken =
oldPrefs.getString(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY, null)
val editor: SharedPreferences.Editor = oldPrefs.edit()
if (getEmail() == null && sharedPrefsEmail != null) {
saveEmail(sharedPrefsEmail)
editor.remove(IterableConstants.SHARED_PREFS_EMAIL_KEY)
IterableLogger.v(
TAG,
"UPDATED: migrated email from SharedPreferences to IterableKeychain"
)
} else if (sharedPrefsEmail != null) {
editor.remove(IterableConstants.SHARED_PREFS_EMAIL_KEY)
}
if (getUserId() == null && sharedPrefsUserId != null) {
saveUserId(sharedPrefsUserId)
editor.remove(IterableConstants.SHARED_PREFS_USERID_KEY)
IterableLogger.v(
TAG,
"UPDATED: migrated userId from SharedPreferences to IterableKeychain"
)
} else if (sharedPrefsUserId != null) {
editor.remove(IterableConstants.SHARED_PREFS_USERID_KEY)
}
if (getAuthToken() == null && sharedPrefsAuthToken != null) {
saveAuthToken(sharedPrefsAuthToken)
editor.remove(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY)
IterableLogger.v(
TAG,
"UPDATED: migrated authToken from SharedPreferences to IterableKeychain"
)
} else if (sharedPrefsAuthToken != null) {
editor.remove(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY)
}
editor.apply()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.iterable.iterableapi

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

class IterableKeychainEncryptedDataMigrator(
private val context: Context,
private val sharedPrefs: SharedPreferences
) {
private val TAG = "IterableKeychainMigrator"

private val encryptedSharedPrefsFileName = "iterable-encrypted-shared-preferences"
private val migrationAttemptedKey = "iterable-encrypted-migration-attempted"

private val emailKey = "iterable-email"
private val userIdKey = "iterable-user-id"
private val authTokenKey = "iterable-auth-token"

fun attemptMigration() {
// Skip if migration was already attempted
if (sharedPrefs.getBoolean(migrationAttemptedKey, false)) {
IterableLogger.v(TAG, "Migration was already attempted, skipping")
return
}

// Mark that we attempted migration
sharedPrefs.edit()
.putBoolean(migrationAttemptedKey, true)
.apply()

// Only attempt migration on Android M and above
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return
}

// Run migration in background thread
Thread {
try {
val masterKeyAlias = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

val encryptedPrefs = EncryptedSharedPreferences.create(
context,
encryptedSharedPrefsFileName,
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// Migrate data
migrateData(encryptedPrefs)

// Clear encrypted prefs after successful migration
encryptedPrefs.edit().clear().apply()

IterableLogger.v(TAG, "Successfully migrated data from encrypted preferences")
} catch (e: Throwable) {
IterableLogger.w(TAG, "Failed to access encrypted preferences, skipping migration", e)
}
}.start()
}

private fun migrateData(encryptedPrefs: SharedPreferences) {
val email = encryptedPrefs.getString(emailKey, null)
val userId = encryptedPrefs.getString(userIdKey, null)
val authToken = encryptedPrefs.getString(authTokenKey, null)

// Only migrate non-null values
sharedPrefs.edit().apply {
email?.let { putString(emailKey, it) }
userId?.let { putString(userIdKey, it) }
authToken?.let { putString(authTokenKey, it) }
}.apply()
}
}

0 comments on commit 284fe2e

Please sign in to comment.