Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MOBL-1834] Remove the cached email from SharedPreferences #360

Merged
merged 5 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = "[email protected]"
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
Loading