Skip to content

Commit

Permalink
[MOBL-1834] Remove the cached email from SharedPreferences (#360)
Browse files Browse the repository at this point in the history
* Renamed encrypted preference file to have the blueshift package name

* Removed the check for email address change to fire identify event

* Fixed the confusion regarding naming

* Fixed the confusion regarding naming of the flag

* Fixed the tests wit additional test case and naming corrections
  • Loading branch information
rahulrvp committed Jun 13, 2024
1 parent 4b03adf commit bc8fe27
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import org.junit.Test

class UserInfoTest {
private lateinit var context: Context
private lateinit var legacyPreference: SharedPreferences
private lateinit var sharedPreferences: SharedPreferences

private fun oldPreferenceFile(context: Context): String {
private fun sharedPreferencesFilename(context: Context): String {
return context.packageName + ".user_info_file"
}

private fun oldPreferenceKey(context: Context): String {
private fun sharedPreferencesKey(context: Context): String {
return context.packageName + ".user_info_key"
}

Expand All @@ -24,10 +24,10 @@ class UserInfoTest {
context = InstrumentationRegistry.getInstrumentation().targetContext

// Reset old preferences
legacyPreference = context.getSharedPreferences(
oldPreferenceFile(context), Context.MODE_PRIVATE
sharedPreferences = context.getSharedPreferences(
sharedPreferencesFilename(context), Context.MODE_PRIVATE
)
legacyPreference.edit().remove(oldPreferenceKey(context)).commit()
sharedPreferences.edit().remove(sharedPreferencesKey(context)).commit()

// Reset new preferences
BlueshiftEncryptedPreferences.init(context)
Expand All @@ -44,18 +44,27 @@ class UserInfoTest {

@Test
fun load_updatedFromOldSDK_returnSameUserInfoObject() {
val emailPreferencesKey = "john.doe@examplepetstore.com"
val emailPreferencesFileName = "${context.packageName}.BsftEmailPrefFile"
val emailPreferences = context.getSharedPreferences(emailPreferencesFileName, Context.MODE_PRIVATE)
emailPreferences.edit().putBoolean(emailPreferencesKey, true).apply()

// Mock the presence of a user object in the old preference.
legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply()
sharedPreferences.edit().putString(sharedPreferencesKey(context), USER_JSON).apply()

// 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)
val name = UserInfo.load(context, false).name
assert(name == JOHN)

// When encryption is not enabled, the user info class should provide the same value
// When encryption is 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)
val encryptedName = UserInfo.load(context, true).name
assert(encryptedName == JOHN)

// When encryption is enabled, the data stored in email preferences (if any) should be deleted.
val status = emailPreferences.getBoolean(emailPreferencesKey, false)
assert(!status)

// Kill the existing instance for the next test.
UserInfo.killInstance()
Expand All @@ -64,7 +73,7 @@ class UserInfoTest {
@Test
fun load_updatedFromOldSDK_copiesTheContentOfOldPrefToNewPref() {
// Mock the presence of a user object in the old preference.
legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply()
sharedPreferences.edit().putString(sharedPreferencesKey(context), USER_JSON).apply()

// case1 : When encryption is not enabled.
val userInfo = UserInfo.load(context, false)
Expand All @@ -82,7 +91,7 @@ class UserInfoTest {
@Test
fun load_updatedFromOldSDK_deletesTheDataInOldPreference() {
// Mock the presence of a user object in the old preference.
legacyPreference.edit().putString(oldPreferenceKey(context), USER_JSON).apply()
sharedPreferences.edit().putString(sharedPreferencesKey(context), USER_JSON).apply()

// case1 : When encryption is not enabled.
val userInfo = UserInfo.load(context, false)
Expand All @@ -91,8 +100,8 @@ class UserInfoTest {
// 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)
val spUserJson = sharedPreferences.getString(sharedPreferencesKey(context), null)
assert(spUserJson == null)

// Kill the existing instance for the next test.
UserInfo.killInstance()
Expand Down
32 changes: 7 additions & 25 deletions android-sdk/src/main/java/com/blueshift/BlueShiftPreference.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,31 +126,6 @@ public static boolean didPushPermissionStatusChange(Context context) {
return true;
}

public static boolean isEmailAlreadyIdentified(Context context, String email) {
boolean result = false;

if (context != null && !TextUtils.isEmpty(email)) {
SharedPreferences preferences = getEmailPreference(context);
if (preferences != null) {
result = preferences.getBoolean(email, false);
}
}

return result;
}

public static void markEmailAsIdentified(Context context, String email) {
if (context != null && !TextUtils.isEmpty(email)) {
SharedPreferences preferences = getEmailPreference(context);
if (preferences != null) {
preferences
.edit()
.putBoolean(email, true)
.apply();
}
}
}

private static SharedPreferences getBlueshiftPreferences(Context context) {
SharedPreferences preferences = null;

Expand All @@ -161,6 +136,13 @@ private static SharedPreferences getBlueshiftPreferences(Context context) {
return preferences;
}

public static void removeCachedEmailAddress(Context context) {
SharedPreferences preferences = getEmailPreference(context);
if (preferences != null) {
preferences.edit().clear().apply();
}
}

private static SharedPreferences getEmailPreference(Context context) {
SharedPreferences preferences = null;

Expand Down
5 changes: 4 additions & 1 deletion android-sdk/src/main/java/com/blueshift/Blueshift.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,10 @@ public void getLiveContentByCustomerId(@NonNull String slot, HashMap<String, Obj
public void initialize(@NonNull Configuration configuration) {
mConfiguration = configuration;

BlueshiftEncryptedPreferences.INSTANCE.init(mContext);
// initialize the encrypted shared preferences if enabled.
if (configuration.shouldSaveUserInfoAsEncrypted()) {
BlueshiftEncryptedPreferences.INSTANCE.init(mContext);
}

BlueshiftAttributesApp.getInstance().init(mContext);
doAppVersionChecks(mContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys

object BlueshiftEncryptedPreferences {
private val PREF_NAME = "blueshift_sdk_preferences"
private const val PREF_NAME = "com.blueshift.encrypted.preferences"

private lateinit var sharedPreferences: SharedPreferences

Expand Down
10 changes: 5 additions & 5 deletions android-sdk/src/main/java/com/blueshift/model/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class Configuration {

// 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;
private boolean saveUserInfoAsEncrypted = false;

public Configuration() {
// Setting default region to the US.
Expand Down Expand Up @@ -98,12 +98,12 @@ public Configuration() {
autoAppOpenInterval = 86400;
}

public boolean shouldEncryptUserInfo() {
return shouldEncryptUserInfo;
public boolean shouldSaveUserInfoAsEncrypted() {
return saveUserInfoAsEncrypted;
}

public void setShouldEncryptUserInfo(boolean shouldEncryptUserInfo) {
this.shouldEncryptUserInfo = shouldEncryptUserInfo;
public void setSaveUserInfoAsEncrypted(boolean saveUserInfoAsEncrypted) {
this.saveUserInfoAsEncrypted = saveUserInfoAsEncrypted;
}

public boolean isPushAppLinksEnabled() {
Expand Down
48 changes: 28 additions & 20 deletions android-sdk/src/main/java/com/blueshift/model/UserInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import androidx.annotation.NonNull;

import com.blueshift.BlueShiftPreference;
import com.blueshift.BlueshiftConstants;
import com.blueshift.BlueshiftEncryptedPreferences;
import com.blueshift.BlueshiftLogger;
Expand Down Expand Up @@ -70,29 +71,30 @@ public static UserInfo getInstance(Context context) {
}
}

private static String getLegacyPreferenceFile(@NonNull Context context) {
private static String getSharedPreferencesFilename(@NonNull Context context) {
return context.getPackageName() + "." + PREF_FILE;
}

private static String getLegacyPreferenceKey(@NonNull Context context) {
private static String getSharedPreferencesKey(@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();
boolean isEncryptionEnabled = configuration != null && configuration.shouldSaveUserInfoAsEncrypted();
return load(context, isEncryptionEnabled);
}

static UserInfo load(Context context, boolean encryptionEnabled) {
return encryptionEnabled ? loadEncrypted(context) : loadLegacy(context);
return encryptionEnabled ? loadFromEncryptedSharedPreferences(context) : loadFromSharedPreferences(context);
}

private static UserInfo loadLegacy(@NonNull Context context) {
private static UserInfo loadFromSharedPreferences(@NonNull Context context) {
UserInfo userInfo = null;

SharedPreferences preferences = context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE);
String json = preferences.getString(getLegacyPreferenceKey(context), null);
BlueshiftLogger.d(TAG, "Loading from SharedPreferences.");
SharedPreferences preferences = context.getSharedPreferences(getSharedPreferencesFilename(context), Context.MODE_PRIVATE);
String json = preferences.getString(getSharedPreferencesKey(context), null);
if (json != null) {
try {
userInfo = new Gson().fromJson(json, UserInfo.class);
Expand All @@ -104,22 +106,28 @@ private static UserInfo loadLegacy(@NonNull Context context) {
return userInfo;
}

private static UserInfo loadEncrypted(@NonNull Context context) {
private static UserInfo loadFromEncryptedSharedPreferences(@NonNull Context context) {
UserInfo userInfo = null;

BlueshiftLogger.d(TAG, "Loading from encrypted preference.");
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.
SharedPreferences pref = context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE);
String legacyJson = pref.getString(getLegacyPreferenceKey(context), null);
if (legacyJson != null) {
SharedPreferences pref = context.getSharedPreferences(getSharedPreferencesFilename(context), Context.MODE_PRIVATE);
String spUserJson = pref.getString(getSharedPreferencesKey(context), null);
if (spUserJson != null) {
BlueshiftLogger.d(TAG, "Found user data inside the SharedPreferences. Copying it to the EncryptedSharedPreferences.");
try {
userInfo = new Gson().fromJson(legacyJson, UserInfo.class);
userInfo = new Gson().fromJson(spUserJson, UserInfo.class);
// Save it to secure store for loading next time.
userInfo.saveEncrypted();
userInfo.saveToEncryptedSharedPreferences();
// Clear the old preference for privacy reasons.
BlueshiftLogger.d(TAG, "Clear the SharedPreferences.");
pref.edit().clear().apply();
// Remove cached email address information (If found)
BlueshiftLogger.d(TAG, "Clear the email from SharedPreferences.");
BlueShiftPreference.removeCachedEmailAddress(context);
} catch (Exception e) {
BlueshiftLogger.e(TAG, e);
}
Expand Down Expand Up @@ -171,25 +179,25 @@ public HashMap<String, Object> toHashMap() {

public void save(@NonNull Context context) {
Configuration configuration = BlueshiftUtils.getConfiguration(context);
boolean isEncryptionEnabled = configuration != null && configuration.shouldEncryptUserInfo();
boolean isEncryptionEnabled = configuration != null && configuration.shouldSaveUserInfoAsEncrypted();
save(context, isEncryptionEnabled);
}

void save(Context context, boolean encryptionEnabled) {
if (encryptionEnabled) {
saveEncrypted();
saveToEncryptedSharedPreferences();
} else {
saveLegacy(context);
saveToSharedPreferences(context);
}
}

private void saveLegacy(Context context) {
private void saveToSharedPreferences(Context context) {
String json = new Gson().toJson(this);
context.getSharedPreferences(getLegacyPreferenceFile(context), Context.MODE_PRIVATE)
.edit().putString(getLegacyPreferenceKey(context), json).apply();
context.getSharedPreferences(getSharedPreferencesFilename(context), Context.MODE_PRIVATE)
.edit().putString(getSharedPreferencesKey(context), json).apply();
}

private void saveEncrypted() {
private void saveToEncryptedSharedPreferences() {
String json = new Gson().toJson(this);
BlueshiftEncryptedPreferences.INSTANCE.putString(PREF_KEY_ENCRYPTED, json);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,24 +224,10 @@ private boolean addDeviceIdAndTokenToParams(String deviceId, String token) {
}

private void doAutoIdentifyCheck(Context context) {
boolean identified = didEmailChange(context) || didPushPermissionChange(context);
boolean identified = didPushPermissionChange(context);
if (identified) BlueshiftLogger.d(LOG_TAG, "Auto identify call fired.");
}

private boolean didEmailChange(Context context) {
UserInfo userInfo = UserInfo.getInstance(context);
if (userInfo != null && !TextUtils.isEmpty(userInfo.getEmail())) {
if (!BlueShiftPreference.isEmailAlreadyIdentified(context, userInfo.getEmail())) {
identify(context);
BlueShiftPreference.markEmailAsIdentified(context, userInfo.getEmail());
BlueshiftLogger.d(LOG_TAG, "Change in email detected. Sending \"identify\".");
return true;
}
}

return false;
}

private boolean didPushPermissionChange(Context context) {
if (BlueShiftPreference.didPushPermissionStatusChange(context)) {
identify(context);
Expand Down

0 comments on commit bc8fe27

Please sign in to comment.