From 61cdfbb03f07f7bc32f34dc61bbca5a985ae900b Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Fri, 3 May 2024 18:12:28 +0530 Subject: [PATCH 1/9] [MOBL-1811] Gradle = 8.6, AGP = 8.4.0, Kotlin = 1.9.23, targetSdkVersion = 34 --- android-sdk/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-sdk/build.gradle b/android-sdk/build.gradle index 730a8e7e..bf4d3226 100644 --- a/android-sdk/build.gradle +++ b/android-sdk/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'org.jetbrains.kotlin.android' ext { PUBLISH_GROUP_ID = 'com.blueshift' PUBLISH_ARTIFACT_ID = 'android-sdk-x' - PUBLISH_VERSION = '3.4.6' + PUBLISH_VERSION = '3.4.8' } android { From f19bea0b480c17a71786146cc0de969592f665bd Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Fri, 3 May 2024 18:25:24 +0530 Subject: [PATCH 2/9] Reverting an unwanted version change --- android-sdk/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-sdk/build.gradle b/android-sdk/build.gradle index bf4d3226..730a8e7e 100644 --- a/android-sdk/build.gradle +++ b/android-sdk/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'org.jetbrains.kotlin.android' ext { PUBLISH_GROUP_ID = 'com.blueshift' PUBLISH_ARTIFACT_ID = 'android-sdk-x' - PUBLISH_VERSION = '3.4.8' + PUBLISH_VERSION = '3.4.6' } android { From c4c90568eb0e78dbf870d030638194d66ebdb782 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Fri, 3 May 2024 17:32:09 +0530 Subject: [PATCH 3/9] first draft of encrypted shared pref implementation is ready --- android-sdk/build.gradle | 3 +- .../BlueshiftEncryptedPreferencesTest.kt | 20 ++++++++++ .../main/java/com/blueshift/Blueshift.java | 2 + .../BlueshiftEncryptedPreferences.kt | 38 +++++++++++++++++++ .../java/com/blueshift/model/UserInfo.java | 18 ++++++--- 5 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt create mode 100644 android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt diff --git a/android-sdk/build.gradle b/android-sdk/build.gradle index 730a8e7e..7097297c 100644 --- a/android-sdk/build.gradle +++ b/android-sdk/build.gradle @@ -12,7 +12,7 @@ android { compileSdk 34 defaultConfig { - minSdkVersion 16 + minSdkVersion 23 targetSdkVersion 34 multiDexEnabled true @@ -53,6 +53,7 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation 'com.google.code.gson:gson:2.8.9' + implementation 'androidx.security:security-crypto:1.0.0' // Firebase implementation 'com.google.firebase:firebase-core:17.4.4' diff --git a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt new file mode 100644 index 00000000..dd19183e --- /dev/null +++ b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt @@ -0,0 +1,20 @@ +package com.blueshift + +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Before +import org.junit.Test + +class BlueshiftEncryptedPreferencesTest { + @Before + fun setUp() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + BlueshiftEncryptedPreferences.init(context) + } + + @Test + fun makeSureSaveAndGetStringWorks() { + BlueshiftEncryptedPreferences.saveString(key = "key", value = "value") + val result = BlueshiftEncryptedPreferences.getString(key = "key", null) + assert(result == "value") + } +} \ No newline at end of file diff --git a/android-sdk/src/main/java/com/blueshift/Blueshift.java b/android-sdk/src/main/java/com/blueshift/Blueshift.java index 23642021..d7d27599 100644 --- a/android-sdk/src/main/java/com/blueshift/Blueshift.java +++ b/android-sdk/src/main/java/com/blueshift/Blueshift.java @@ -352,6 +352,8 @@ public void getLiveContentByCustomerId(@NonNull String slot, HashMap toHashMap() { } public void save(Context context) { - context.getSharedPreferences(getPrefFile(context), Context.MODE_PRIVATE) - .edit() - .putString(getPrefKey(context), new Gson().toJson(this)) - .apply(); + String json = new Gson().toJson(this); + BlueshiftEncryptedPreferences.INSTANCE.saveString(getPrefKey(context), json); +// BlueshiftEncryptedPreferences.INSTANCE.putString(getPrefKey(context), json); +// context.getSharedPreferences(getPrefFile(context), Context.MODE_PRIVATE) +// .edit() +// .putString(getPrefKey(context), new Gson().toJson(this)) +// .apply(); } public String getEmail() { From 7199373dedb1b753fcb44895cd99143e109a2272 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Fri, 3 May 2024 18:18:36 +0530 Subject: [PATCH 4/9] Added tests for the get and save methods --- .../com/blueshift/BlueshiftEncryptedPreferencesTest.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt index dd19183e..fe3548fe 100644 --- a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt +++ b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt @@ -12,9 +12,16 @@ class BlueshiftEncryptedPreferencesTest { } @Test - fun makeSureSaveAndGetStringWorks() { + fun savedValueIsReturnedWhenCorrectKeyIsProvided() { BlueshiftEncryptedPreferences.saveString(key = "key", value = "value") val result = BlueshiftEncryptedPreferences.getString(key = "key", null) assert(result == "value") } + + @Test + fun defaultValueIsReturnedWhenIncorrectKeyIsProvided() { + BlueshiftEncryptedPreferences.saveString(key = "key", value = "value") + val result = BlueshiftEncryptedPreferences.getString(key = "wrong_key", "default") + assert(result == "default") + } } \ No newline at end of file From 2faf42efafa931cabc8c011dba16ab40b7d5a2d4 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Fri, 3 May 2024 19:21:03 +0530 Subject: [PATCH 5/9] Added code for migration when needed. --- .../BlueshiftEncryptedPreferencesTest.kt | 4 +- .../BlueshiftEncryptedPreferences.kt | 2 +- .../java/com/blueshift/model/UserInfo.java | 57 +++++++++++-------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt index fe3548fe..2b5b1f10 100644 --- a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt +++ b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt @@ -13,14 +13,14 @@ class BlueshiftEncryptedPreferencesTest { @Test fun savedValueIsReturnedWhenCorrectKeyIsProvided() { - BlueshiftEncryptedPreferences.saveString(key = "key", value = "value") + BlueshiftEncryptedPreferences.putString(key = "key", value = "value") val result = BlueshiftEncryptedPreferences.getString(key = "key", null) assert(result == "value") } @Test fun defaultValueIsReturnedWhenIncorrectKeyIsProvided() { - BlueshiftEncryptedPreferences.saveString(key = "key", value = "value") + BlueshiftEncryptedPreferences.putString(key = "key", value = "value") val result = BlueshiftEncryptedPreferences.getString(key = "wrong_key", "default") assert(result == "default") } diff --git a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt index ba2f81bd..323bb47e 100644 --- a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt +++ b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt @@ -22,7 +22,7 @@ object BlueshiftEncryptedPreferences { ) } - fun saveString(key: String, value: String?) { + fun putString(key: String, value: String?) { if (::sharedPreferences.isInitialized) { sharedPreferences.edit().putString(key, value).apply() } diff --git a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java index 8bf3341e..e3ddb219 100644 --- a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java +++ b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java @@ -1,12 +1,12 @@ package com.blueshift.model; import android.content.Context; +import android.content.SharedPreferences; import com.blueshift.BlueshiftConstants; import com.blueshift.BlueshiftEncryptedPreferences; import com.blueshift.BlueshiftLogger; import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -22,8 +22,7 @@ */ public class UserInfo { private static final String TAG = "UserInfo"; - private static final String PREF_FILE = "user_info_file"; - private static final String PREF_KEY = "user_info_key"; + private static final String PREF_KEY = "user_info"; private static final Boolean lock = false; private String email; @@ -61,24 +60,34 @@ public static UserInfo getInstance(Context context) { } } - private static String getPrefFile(Context context) { - return context.getPackageName() + "." + PREF_FILE; - } - - private static String getPrefKey(Context context) { - return context.getPackageName() + "." + PREF_KEY; - } - private static UserInfo load(Context context) { UserInfo userInfo = null; - String json = BlueshiftEncryptedPreferences.INSTANCE.getString(getPrefKey(context), null); -// String json = context.getSharedPreferences(getPrefFile(context), Context.MODE_PRIVATE) -// .getString(getPrefKey(context), null); - if (json != null) { + String json = BlueshiftEncryptedPreferences.INSTANCE.getString(PREF_KEY, null); + if (json == null) { + // The new secure store doesn't have the user info. Let's check in the old preference + // file and copy over the data if present. + String oldPreferenceFile = context.getPackageName() + ".user_info_file"; + String oldPreferenceKey = context.getPackageName() + ".user_info_key"; + + SharedPreferences pref = context.getSharedPreferences(oldPreferenceFile, Context.MODE_PRIVATE); + String oldJson = pref.getString(oldPreferenceKey, null); + if (oldJson != null) { + try { + userInfo = new Gson().fromJson(oldJson, UserInfo.class); + // Save it to secure store for loading next time. + userInfo.save(); + // Clear the old preference for privacy reasons. + pref.edit().clear().apply(); + } catch (Exception e) { + BlueshiftLogger.e(TAG, e); + } + } + } else { + // The new secure store has the user info. Let's load it. try { userInfo = new Gson().fromJson(json, UserInfo.class); - } catch (JsonSyntaxException e) { + } catch (Exception e) { BlueshiftLogger.e(TAG, e); } } @@ -119,14 +128,16 @@ public HashMap toHashMap() { return map; } + + // This method is deprecated. Use the save() method instead. + @Deprecated public void save(Context context) { + save(); + } + + public void save() { String json = new Gson().toJson(this); - BlueshiftEncryptedPreferences.INSTANCE.saveString(getPrefKey(context), json); -// BlueshiftEncryptedPreferences.INSTANCE.putString(getPrefKey(context), json); -// context.getSharedPreferences(getPrefFile(context), Context.MODE_PRIVATE) -// .edit() -// .putString(getPrefKey(context), new Gson().toJson(this)) -// .apply(); + BlueshiftEncryptedPreferences.INSTANCE.putString(PREF_KEY, json); } public String getEmail() { @@ -275,6 +286,6 @@ public void clear(Context context) { instance.dateOfBirth = null; } - save(context); + save(); } } From 57430d26160f237e79aadc67103b664d7695eba3 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Mon, 6 May 2024 13:06:27 +0530 Subject: [PATCH 6/9] Added test cases to make sure the new preference is backward compatible --- .../java/com/blueshift/model/UserInfoTest.kt | 92 +++++++++++++++++++ .../BlueshiftEncryptedPreferences.kt | 6 ++ .../java/com/blueshift/model/UserInfo.java | 4 + 3 files changed, 102 insertions(+) create mode 100644 android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt diff --git a/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt new file mode 100644 index 00000000..a0cdbc18 --- /dev/null +++ b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt @@ -0,0 +1,92 @@ +package com.blueshift.model + +import android.content.Context +import android.content.SharedPreferences +import androidx.test.platform.app.InstrumentationRegistry +import com.blueshift.BlueshiftEncryptedPreferences +import org.junit.Before +import org.junit.Test + +class UserInfoTest { + private lateinit var context: Context + private lateinit var legacyPreference: SharedPreferences + + private fun oldPreferenceFile(context: Context): String { + return context.packageName + ".user_info_file" + } + + private fun oldPreferenceKey(context: Context): String { + return context.packageName + ".user_info_key" + } + + @Before + fun setUp() { + context = InstrumentationRegistry.getInstrumentation().targetContext + + // Reset old preferences + legacyPreference = context.getSharedPreferences( + oldPreferenceFile(context), Context.MODE_PRIVATE + ) + legacyPreference.edit().remove(oldPreferenceKey(context)).commit() + + // Reset new preferences + BlueshiftEncryptedPreferences.init(context) + BlueshiftEncryptedPreferences.remove(PREF_KEY) + } + + @Test + fun load_newInstall_returnEmptyUserInfoObject() { + // For a fresh installation, the shared preferences will not contain any data. + // So, the user info class should provide an instance without any value for its members. + val user = UserInfo.getInstance(context) + assert(user.name == null) + } + + @Test + fun load_updatedFromOldSDK_returnSameUserInfoObject() { + // Mock the presence of a user object in the old preference. + legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() + + // The loaded user info object should provide the same value for its members as we saved in the old preference. + val name = UserInfo.getInstance(context).name + assert(name == "name") + + // Kill the existing instance for the next test. + UserInfo.killInstance() + } + + @Test + fun load_updatedFromOldSDK_copiesTheContentOfOldPrefToNewPref() { + // Mock the presence of a user object in the old preference. + legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() + + UserInfo.getInstance(context) + + // Double check the value stored in the secure store is same as the value in the old preference. + val json = BlueshiftEncryptedPreferences.getString(PREF_KEY, null) + assert(USER_JSON == json) + + // Kill the existing instance for the next test. + UserInfo.killInstance() + } + + @Test + fun load_updatedFromOldSDK_deletesTheDataInOldPreference() { + // Mock the presence of a user object in the old preference. + legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() + + UserInfo.getInstance(context) + + // Make sure the value stored in the old preferences is removed after copying it to the new preferences. + val legacyJson = legacyPreference.getString(oldPreferenceKey(context), null) + assert(legacyJson == null) + + // Kill the existing instance for the next test. + UserInfo.killInstance() + } + + companion object { + const val PREF_KEY = "user_info" + const val USER_JSON = "{\"joined_at\":0,\"name\":\"name\",\"unsubscribed\":false}" + } +} \ No newline at end of file diff --git a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt index 323bb47e..d84c1be2 100644 --- a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt +++ b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt @@ -22,6 +22,12 @@ object BlueshiftEncryptedPreferences { ) } + fun remove(key: String) { + if (::sharedPreferences.isInitialized) { + sharedPreferences.edit().remove(key).apply() + } + } + fun putString(key: String, value: String?) { if (::sharedPreferences.isInitialized) { sharedPreferences.edit().putString(key, value).apply() diff --git a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java index e3ddb219..7bdf327d 100644 --- a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java +++ b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java @@ -49,6 +49,10 @@ private UserInfo() { unsubscribed = false; } + static void killInstance() { + instance = null; + } + public static UserInfo getInstance(Context context) { synchronized (lock) { if (instance == null) { From e9f872c44c2cfd969511ebf770b30dada010b997 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Mon, 6 May 2024 13:08:28 +0530 Subject: [PATCH 7/9] Deprecated the clear(context) method --- android-sdk/src/main/java/com/blueshift/model/UserInfo.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java index 7bdf327d..b55c390a 100644 --- a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java +++ b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java @@ -269,7 +269,13 @@ public Date getDateOfBirth() { return dateOfBirth; } + // This method is deprecated. Use the clear() method instead. + @Deprecated public void clear(Context context) { + clear(); + } + + public void clear() { synchronized (lock) { instance.email = null; instance.email_hash = null; From cdd7cd43d88a414a6c12fd20c2294a61b143ea6b Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Mon, 6 May 2024 15:48:33 +0530 Subject: [PATCH 8/9] Added additional test case --- .../BlueshiftEncryptedPreferencesTest.kt | 16 +++++++++++++++- .../java/com/blueshift/model/UserInfoTest.kt | 2 +- .../blueshift/BlueshiftEncryptedPreferences.kt | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt index 2b5b1f10..f192e5d5 100644 --- a/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt +++ b/android-sdk/src/androidTest/java/com/blueshift/BlueshiftEncryptedPreferencesTest.kt @@ -24,4 +24,18 @@ class BlueshiftEncryptedPreferencesTest { val result = BlueshiftEncryptedPreferences.getString(key = "wrong_key", "default") assert(result == "default") } -} \ No newline at end of file + + @Test + fun storedValueIsRemovedWhenCallingRemoveMethod() { + BlueshiftEncryptedPreferences.putString(key = "key", value = "value") + + val result1 = BlueshiftEncryptedPreferences.getString(key = "key", null) + assert(result1 == "value") + + BlueshiftEncryptedPreferences.remove(key = "key") + + val result2 = BlueshiftEncryptedPreferences.getString(key = "key", null) + assert(result2 == null) + } + +} diff --git a/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt index a0cdbc18..15c01144 100644 --- a/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt +++ b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt @@ -89,4 +89,4 @@ class UserInfoTest { const val PREF_KEY = "user_info" const val USER_JSON = "{\"joined_at\":0,\"name\":\"name\",\"unsubscribed\":false}" } -} \ No newline at end of file +} diff --git a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt index d84c1be2..a3992aea 100644 --- a/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt +++ b/android-sdk/src/main/java/com/blueshift/BlueshiftEncryptedPreferences.kt @@ -41,4 +41,4 @@ object BlueshiftEncryptedPreferences { defaultValue } } -} \ No newline at end of file +} From 0e1ec8080731207567f97f1f31cdb8f938157d93 Mon Sep 17 00:00:00 2001 From: Rahul Raveendran Date: Mon, 27 May 2024 17:37:32 +0530 Subject: [PATCH 9/9] Added configuration variable to opt in for encryption feature --- .../java/com/blueshift/model/UserInfoTest.kt | 28 ++++-- .../com/blueshift/model/Configuration.java | 12 +++ .../java/com/blueshift/model/UserInfo.java | 92 ++++++++++++++----- 3 files changed, 101 insertions(+), 31 deletions(-) diff --git a/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt index 15c01144..31be6d49 100644 --- a/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt +++ b/android-sdk/src/androidTest/java/com/blueshift/model/UserInfoTest.kt @@ -47,9 +47,15 @@ class UserInfoTest { // Mock the presence of a user object in the old preference. legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() - // The loaded user info object should provide the same value for its members as we saved in the old preference. - val name = UserInfo.getInstance(context).name - assert(name == "name") + // When encryption is not enabled, the user info class should provide the same value + // for its members as we saved in the old preference. + val userinfo = UserInfo.load(context, false) + assert(userinfo.name == JOHN) + + // When encryption is not enabled, the user info class should provide the same value + // for its members as we saved in the old preference. + val userinfo2 = UserInfo.load(context, true) + assert(userinfo2.name == JOHN) // Kill the existing instance for the next test. UserInfo.killInstance() @@ -60,9 +66,12 @@ class UserInfoTest { // Mock the presence of a user object in the old preference. legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() - UserInfo.getInstance(context) + // case1 : When encryption is not enabled. + val userInfo = UserInfo.load(context, false) + assert(userInfo.name == JOHN) - // Double check the value stored in the secure store is same as the value in the old preference. + // case2 : When encryption is enabled. + UserInfo.load(context, true) val json = BlueshiftEncryptedPreferences.getString(PREF_KEY, null) assert(USER_JSON == json) @@ -75,8 +84,12 @@ class UserInfoTest { // Mock the presence of a user object in the old preference. legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply() - UserInfo.getInstance(context) + // case1 : When encryption is not enabled. + val userInfo = UserInfo.load(context, false) + assert(userInfo.name == JOHN) + // case2 : When encryption is enabled. + UserInfo.load(context, true) // Make sure the value stored in the old preferences is removed after copying it to the new preferences. val legacyJson = legacyPreference.getString(oldPreferenceKey(context), null) assert(legacyJson == null) @@ -86,7 +99,8 @@ class UserInfoTest { } companion object { + const val JOHN = "John" const val PREF_KEY = "user_info" - const val USER_JSON = "{\"joined_at\":0,\"name\":\"name\",\"unsubscribed\":false}" + const val USER_JSON = "{\"joined_at\":0,\"name\":\"$JOHN\",\"unsubscribed\":false}" } } diff --git a/android-sdk/src/main/java/com/blueshift/model/Configuration.java b/android-sdk/src/main/java/com/blueshift/model/Configuration.java index 882edb7b..fb44d716 100644 --- a/android-sdk/src/main/java/com/blueshift/model/Configuration.java +++ b/android-sdk/src/main/java/com/blueshift/model/Configuration.java @@ -58,6 +58,10 @@ public class Configuration { private Blueshift.DeviceIdSource deviceIdSource; private String customDeviceId; + // Defines is we should store user info in plain text or in encrypted form. + // Default value is false to make it backward compatible. + private boolean shouldEncryptUserInfo = false; + public Configuration() { // Setting default region to the US. region = BlueshiftRegion.US; @@ -94,6 +98,14 @@ public Configuration() { autoAppOpenInterval = 86400; } + public boolean shouldEncryptUserInfo() { + return shouldEncryptUserInfo; + } + + public void setShouldEncryptUserInfo(boolean shouldEncryptUserInfo) { + this.shouldEncryptUserInfo = shouldEncryptUserInfo; + } + public boolean isPushAppLinksEnabled() { return pushAppLinksEnabled; } diff --git a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java index b55c390a..e407210d 100644 --- a/android-sdk/src/main/java/com/blueshift/model/UserInfo.java +++ b/android-sdk/src/main/java/com/blueshift/model/UserInfo.java @@ -3,10 +3,14 @@ import android.content.Context; import android.content.SharedPreferences; +import androidx.annotation.NonNull; + import com.blueshift.BlueshiftConstants; import com.blueshift.BlueshiftEncryptedPreferences; import com.blueshift.BlueshiftLogger; +import com.blueshift.util.BlueshiftUtils; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -22,7 +26,9 @@ */ public class UserInfo { private static final String TAG = "UserInfo"; - private static final String PREF_KEY = "user_info"; + private static final String PREF_FILE = "user_info_file"; + private static final String PREF_KEY = "user_info_key"; + private static final String PREF_KEY_ENCRYPTED = "user_info"; private static final Boolean lock = false; private String email; @@ -64,23 +70,54 @@ public static UserInfo getInstance(Context context) { } } - private static UserInfo load(Context context) { + private static String getLegacyPreferenceFile(@NonNull Context context) { + return context.getPackageName() + "." + PREF_FILE; + } + + private static String getLegacyPreferenceKey(@NonNull Context context) { + return context.getPackageName() + "." + PREF_KEY; + } + + private static UserInfo load(@NonNull Context context) { + Configuration configuration = BlueshiftUtils.getConfiguration(context); + boolean isEncryptionEnabled = configuration != null && configuration.shouldEncryptUserInfo(); + return load(context, isEncryptionEnabled); + } + + static UserInfo load(Context context, boolean encryptionEnabled) { + return encryptionEnabled ? loadEncrypted(context) : loadLegacy(context); + } + + private static UserInfo loadLegacy(@NonNull Context context) { UserInfo userInfo = null; - String json = BlueshiftEncryptedPreferences.INSTANCE.getString(PREF_KEY, null); + SharedPreferences preferences = context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE); + String json = preferences.getString(getLegacyPreferenceKey(context), null); + if (json != null) { + try { + userInfo = new Gson().fromJson(json, UserInfo.class); + } catch (JsonSyntaxException e) { + BlueshiftLogger.e(TAG, e); + } + } + + return userInfo; + } + + private static UserInfo loadEncrypted(@NonNull Context context) { + UserInfo userInfo = null; + + String json = BlueshiftEncryptedPreferences.INSTANCE.getString(PREF_KEY_ENCRYPTED, null); if (json == null) { // The new secure store doesn't have the user info. Let's check in the old preference // file and copy over the data if present. - String oldPreferenceFile = context.getPackageName() + ".user_info_file"; - String oldPreferenceKey = context.getPackageName() + ".user_info_key"; - - SharedPreferences pref = context.getSharedPreferences(oldPreferenceFile, Context.MODE_PRIVATE); - String oldJson = pref.getString(oldPreferenceKey, null); - if (oldJson != null) { + SharedPreferences pref = context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE); + String legacyJson = pref.getString(getLegacyPreferenceKey(context), null); + if (legacyJson != null) { try { - userInfo = new Gson().fromJson(oldJson, UserInfo.class); + userInfo = new Gson().fromJson(legacyJson, UserInfo.class); // Save it to secure store for loading next time. - userInfo.save(); + userInfo.saveEncrypted(); // Clear the old preference for privacy reasons. pref.edit().clear().apply(); } catch (Exception e) { @@ -132,16 +169,29 @@ public HashMap toHashMap() { return map; } + public void save(@NonNull Context context) { + Configuration configuration = BlueshiftUtils.getConfiguration(context); + boolean isEncryptionEnabled = configuration != null && configuration.shouldEncryptUserInfo(); + save(context, isEncryptionEnabled); + } + + void save(Context context, boolean encryptionEnabled) { + if (encryptionEnabled) { + saveEncrypted(); + } else { + saveLegacy(context); + } + } - // This method is deprecated. Use the save() method instead. - @Deprecated - public void save(Context context) { - save(); + private void saveLegacy(Context context) { + String json = new Gson().toJson(this); + context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE) + .edit().putString(getLegacyPreferenceKey(context), json).apply(); } - public void save() { + private void saveEncrypted() { String json = new Gson().toJson(this); - BlueshiftEncryptedPreferences.INSTANCE.putString(PREF_KEY, json); + BlueshiftEncryptedPreferences.INSTANCE.putString(PREF_KEY_ENCRYPTED, json); } public String getEmail() { @@ -269,13 +319,7 @@ public Date getDateOfBirth() { return dateOfBirth; } - // This method is deprecated. Use the clear() method instead. - @Deprecated public void clear(Context context) { - clear(); - } - - public void clear() { synchronized (lock) { instance.email = null; instance.email_hash = null; @@ -296,6 +340,6 @@ public void clear() { instance.dateOfBirth = null; } - save(); + save(context); } }