diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/LocalDataStore.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/LocalDataStore.java index d584dc899..734cd098d 100644 --- a/clevertap-core/src/main/java/com/clevertap/android/sdk/LocalDataStore.java +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/LocalDataStore.java @@ -574,7 +574,7 @@ public void run() { if (profile.get(piiKey) != null) { Object value = profile.get(piiKey); if (value instanceof String) { - String encrypted = cryptHandler.encrypt((String) value, piiKey); + String encrypted = cryptHandler.encrypt((String) value, piiKey, CryptHandler.EncryptionAlgorithm.AES_GCM); if (encrypted == null) { passFlag = false; continue; diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginInfoProvider.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginInfoProvider.java index 99c887446..ca8733828 100644 --- a/clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginInfoProvider.java +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginInfoProvider.java @@ -62,7 +62,7 @@ public void cacheGUIDForIdentifier(String guid, String key, String identifier) { } try { cache.put(cacheKey, guid); - String encryptedCache = cryptHandler.encrypt(cache.toString(), key); + String encryptedCache = cryptHandler.encrypt(cache.toString(), key, CryptHandler.EncryptionAlgorithm.AES_GCM); if(encryptedCache == null) { encryptedCache = cache.toString(); cryptHandler.updateMigrationFailureCount(false); diff --git a/clevertap-core/src/test/java/com/clevertap/android/sdk/login/LoginInfoProviderTest.kt b/clevertap-core/src/test/java/com/clevertap/android/sdk/login/LoginInfoProviderTest.kt index 3d46db3c9..4c0906e94 100644 --- a/clevertap-core/src/test/java/com/clevertap/android/sdk/login/LoginInfoProviderTest.kt +++ b/clevertap-core/src/test/java/com/clevertap/android/sdk/login/LoginInfoProviderTest.kt @@ -1,203 +1,133 @@ package com.clevertap.android.sdk.login -import android.content.Context import com.clevertap.android.sdk.CleverTapInstanceConfig import com.clevertap.android.sdk.CoreMetaData import com.clevertap.android.sdk.DeviceInfo import com.clevertap.android.sdk.cryption.CryptHandler import com.clevertap.android.shared.test.BaseTestCase +import io.mockk.* import org.json.JSONObject -import org.junit.Ignore +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito -import org.mockito.Mockito.anyString -import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.verify -import org.robolectric.RobolectricTestRunner -import kotlin.test.assertEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue - -@Ignore -@RunWith(RobolectricTestRunner::class) -class LoginInfoProviderTest: BaseTestCase() { + +class LoginInfoProviderTest : BaseTestCase() { private lateinit var defConfig: CleverTapInstanceConfig private lateinit var deviceInfo: DeviceInfo private lateinit var coreMetaData: CoreMetaData private lateinit var cryptHandler: CryptHandler - private lateinit var loginInfoProvider: LoginInfoProvider - private lateinit var loginInfoProviderSpy: LoginInfoProvider override fun setUp() { super.setUp() coreMetaData = CoreMetaData() defConfig = CleverTapInstanceConfig.createInstance(appCtx, "id", "token", "region") - deviceInfo = Mockito.mock(DeviceInfo::class.java) - cryptHandler = Mockito.mock(CryptHandler::class.java) - loginInfoProvider = LoginInfoProvider( - appCtx, - defConfig, - cryptHandler + deviceInfo = mockk(relaxed = true) + cryptHandler = mockk(relaxed = true) + loginInfoProvider = spyk( + LoginInfoProvider( + appCtx, + defConfig, + cryptHandler + ) ) - loginInfoProviderSpy = Mockito.spy(loginInfoProvider) } @Test - fun test_cacheGUIDForIdentifier_when_all_keys_are_correct_all_values_are_saved() { - val guid = "__1234567" + fun `cacheGUIDForIdentifier saves all values when all keys are correct`() { + val guid = "__789" val key = "Email" val identifier = "abc@gmail.com" - Mockito.`when`(cryptHandler.encrypt(anyString(), anyString())) - .thenReturn("dummy_encrypted") - val a = JSONObject().apply { - put("Phone_id1","__1234567") - } - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn(a) - loginInfoProviderSpy.cacheGUIDForIdentifier(guid, key, identifier) + val initialGuids = JSONObject().apply { put("Phone_id1", "__1234567") } - // Capture arguments passed to setCachedGUIDsAndLength - val keyCaptor = argumentCaptor() - val valueCaptor = argumentCaptor() - verify(loginInfoProviderSpy).setCachedGUIDsAndLength(keyCaptor.capture(), valueCaptor.capture()) + every { cryptHandler.encrypt(any(), any(), CryptHandler.EncryptionAlgorithm.AES_GCM) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids - // Assert captured arguments - assertEquals("dummy_encrypted", keyCaptor.firstValue) // Replace "Expected key" with the expected key - assertEquals(2, valueCaptor.firstValue) + loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) + + verify { loginInfoProvider.setCachedGUIDsAndLength("dummy_encrypted", 2) } } @Test - fun test_cacheGUIDForIdentifier_when_key_is_empty_value_is_saved_without_key() { + fun `cacheGUIDForIdentifier saves value without key when key is empty`() { val guid = "__1234567" val key = "" val identifier = "abc@gmail.com" - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") + val initialGuids = JSONObject().apply { put("Phone_id1", "__1234567") } - loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) + every { cryptHandler.encrypt(any(), any(), CryptHandler.EncryptionAlgorithm.AES_GCM) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) + loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - assertEquals( - "{\"_dummy_encrypted\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id", "") - ) + verify { loginInfoProvider.setCachedGUIDsAndLength("dummy_encrypted", 2) } } @Test - fun test_cacheGUIDForIdentifier_when_identifier_is_empty_value_is_saved() { + fun `cacheGUIDForIdentifier saves value when identifier is empty`() { val guid = "__1234567" val key = "Email" val identifier = "" - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") + val initialGuids = JSONObject().apply { put("Phone_id1", "__1234567") } - loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) + every { cryptHandler.encrypt(any(), any(), CryptHandler.EncryptionAlgorithm.AES_GCM) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) + loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - assertEquals( - "{\"Email_dummy_encrypted\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id", "") - ) + verify { loginInfoProvider.setCachedGUIDsAndLength("dummy_encrypted", 2) } } @Test - fun test_cacheGUIDForIdentifier_when_guid_is_empty_value_is_saved_without_guid() { + fun `cacheGUIDForIdentifier does not save value when GUID is empty`() { val guid = "" val key = "Email" val identifier = "abc@gmail.com" + val initialGuids = JSONObject().apply { put("Phone_id1", "__1234567") } - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") + every { cryptHandler.encrypt(any(), any(), CryptHandler.EncryptionAlgorithm.AES_GCM) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - - assertEquals( - "{\"Email_dummy_encrypted\":\"\"}", - sharedPreferences.getString("cachedGUIDsKey:id", "") - ) + verify(exactly = 0) { loginInfoProvider.setCachedGUIDsAndLength("dummy_encrypted", 2) } } @Test - fun test_cacheGUIDForIdentifier_when_all_keys_are_correct_but_encryption_fails_should_save_plain_identifier() { + fun `cacheGUIDForIdentifier saves plain identifier when encryption fails`() { val guid = "__1234567" val key = "email" val identifier = "abc@gmail.com" + val cryptedKey = "${key}_$identifier" + val initialGuids = JSONObject().apply { put("Phone_id1", "__1234567") } + val finalGuids = JSONObject().apply { + put("Phone_id1", "__1234567") + put(cryptedKey, guid) + } - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn(null) + every { cryptHandler.encrypt(any(), any(), CryptHandler.EncryptionAlgorithm.AES_GCM) } returns null + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - assertEquals( - "{\"email_abc@gmail.com\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id", "") - ) + verify { cryptHandler.updateMigrationFailureCount(false) } + verify { loginInfoProvider.setCachedGUIDsAndLength(finalGuids.toString(), 2) } } @Test - fun test_getGUIDForIdentifier_when_guid_is_already_cached() { + fun `getGUIDForIdentifier returns GUID when already cached`() { val guid = "__1234567" val key = "email" val identifier = "abc@gmail.com" + val cryptedKey = "${key}_$identifier" + val initialGuids = JSONObject().apply { + put("Phone_id1", "__789") + put(cryptedKey, guid) + } - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") - - loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, identifier) - - assertEquals(guid, actualGuid) - } - - @Test - fun test_getGUIDForIdentifier_when_guid_for_identifier_or_encryptedIdentifier_is_not_present_in_cache() { - val key = "email" - val identifier = "abc@gmail.com" - - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") - - val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, identifier) - - assertNull(actualGuid) - } - - @Test - fun test_getGUIDForIdentifier_when_encryption_fails_and_guid_is_not_present_for_plain_identifier_either() { - val key = "email" - val identifier = "abc@gmail.com" - - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn(null) - - val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, identifier) - - assertNull(actualGuid) - } - - @Test - fun test_getGUIDForIdentifier_when_encryption_fails_but_guid_is_present_for_plain_identifier() { - val guid = "__1234567" - val key = "email" - val identifier = "abc@gmail.com" - - // Replicate a situation when encryption level is 1 but migration was unsuccessful and hence one of the identifier is un-encrypted - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn(identifier) - loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - - // Encryption fails - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn(null) + every { cryptHandler.encrypt(identifier, key) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, identifier) @@ -205,201 +135,35 @@ class LoginInfoProviderTest: BaseTestCase() { } @Test - fun test_getGUIDForIdentifier_when_encryption_passes_but_guid_is_present_for_plain_identifier() { + fun `getGUIDForIdentifier returns null when GUID is not cached`() { val guid = "__1234567" val key = "email" val identifier = "abc@gmail.com" + val cryptedKey = "${key}_$identifier" + val initialGuids = JSONObject().apply { put(cryptedKey, guid) } - // Replicate a situation when encryption level is 1 but migration was unsuccessful and hence one of the identifier is un-encrypted - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn(identifier) - loginInfoProvider.cacheGUIDForIdentifier(guid, key, identifier) - - // Encryption fails - Mockito.`when`(cryptHandler.encrypt(identifier, key)) - .thenReturn("dummy_encrypted") - - val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, identifier) - - assertEquals(guid, actualGuid) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_email_and_identity_remove_provided_key() { - val guid = "__1234567" - val key = "Email" - - val jsonObj = JSONObject() - jsonObj.put("Email_donjoe2862@gmail.com","__1234567") - jsonObj.put("Identity_00002","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) + every { cryptHandler.encrypt(identifier, key) } returns "dummy_encrypted" + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) + val actualGuid = loginInfoProvider.getGUIDForIdentifier(key, "not_cached@gmail.com") - //Assert - assertEquals("{\"Identity_00002\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_email_removes_cached_shared_prefs_key() { - val guid = "__1234567" - val key = "Email" - - val jsonObj = JSONObject() - jsonObj.put("Email_donjoe2862@gmail.com","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - //Assert - assertEquals("", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_lowercase_email_removes_cached_shared_prefs_key() { - val guid = "__1234567" - val key = "email" - - val jsonObj = JSONObject() - jsonObj.put("Email_donjoe2862@gmail.com","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - //Assert - assertEquals("", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_lowercase_email_removes_cached_shared_prefs_lowercase_key() { - val guid = "__1234567" - val key = "Email" - - val jsonObj = JSONObject() - jsonObj.put("email_donjoe2862@gmail.com","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - //Assert - assertEquals("", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_lowercase_email_and_identity_removes_cached_shared_prefs_lowercase_key(){ - val guid = "__1234567" - val key = "Email" - - val jsonObj = JSONObject() - jsonObj.put("email_donjoe2862@gmail.com","__1234567") - jsonObj.put("identity_00002","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - //Assert - assertEquals("{\"identity_00002\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_Email_and_lowercase_identity_removes_cached_shared_prefs_key(){ - val guid = "__1234567" - val key = "Email" - - val jsonObj = JSONObject() - jsonObj.put("Email_donjoe2862@gmail.com","__1234567") - jsonObj.put("identity_00002","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - val sharedPreferences = appCtx.getSharedPreferences("WizRocket", Context.MODE_PRIVATE) - - //Assert - assertEquals("{\"identity_00002\":\"__1234567\"}", - sharedPreferences.getString("cachedGUIDsKey:id","")) - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_when_cache_data_contains_random_key_and_Identity_removes_cached_shared_prefs_key(){ - val guid = "__1234567" - val key = "abcxyz" - - val jsonObj = JSONObject() - jsonObj.put("Identity_00002","__1234567") - jsonObj.put("Email_donjoe2862@gmail.com","__1234567") - - Mockito.`when`(loginInfoProviderSpy.decryptedCachedGUIDs).thenReturn( - jsonObj) - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - - //Assert - assertEquals("{\"Identity_00002\":\"__1234567\",\"Email_donjoe2862@gmail.com\":\"__1234567\"}", - jsonObj.toString()) + assertNull(actualGuid) } @Test - fun test_removeValueFromCachedGUIDForIdentifier_key_is_null_and_guid_has_value_should_do_nothing(){ + fun `removeValueFromCachedGUIDForIdentifier removes value by key`() { val guid = "__1234567" - val key = null - - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - - //Assert - Mockito.verify(loginInfoProviderSpy,Mockito.never()).decryptedCachedGUIDs - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_key_has_value_and_guid_is_null_should_do_nothing(){ - val guid = null val key = "Email" + val initialGuids = JSONObject().apply { + put("Email_donjoe2862@gmail.com", "__1234567") + put("Identity_00002", "__1234567") + } + val resultGuids = JSONObject().apply { put("Identity_00002", "__1234567") } - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) - - //Assert - Mockito.verify(loginInfoProviderSpy,Mockito.never()).decryptedCachedGUIDs - } - - @Test - fun test_removeValueFromCachedGUIDForIdentifier_key_is_null_and_guid_is_null_should_do_nothing(){ - val guid = null - val key = null + every { loginInfoProvider.getDecryptedCachedGUIDs() } returns initialGuids - //Act - loginInfoProviderSpy.removeValueFromCachedGUIDForIdentifier(guid, key) + loginInfoProvider.removeValueFromCachedGUIDForIdentifier(guid, key) - //Assert - Mockito.verify(loginInfoProviderSpy,Mockito.never()).decryptedCachedGUIDs + verify { loginInfoProvider.setCachedGUIDsAndLength(resultGuids.toString(), 1) } } -} \ No newline at end of file +}