From f26e2b177d29c2af1dd55bb4cb37e52aec6d5497 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Fri, 26 Jul 2024 13:29:49 +0200 Subject: [PATCH 1/3] Diagnostics: Remove unused anonymizer --- .../revenuecat/purchases/PurchasesFactory.kt | 3 - .../revenuecat/purchases/common/Anonymizer.kt | 36 ----- .../diagnostics/DiagnosticsAnonymizer.kt | 13 -- .../common/diagnostics/DiagnosticsTracker.kt | 6 +- .../purchases/common/AnonymizerTest.kt | 140 ------------------ .../diagnostics/DiagnosticsAnonymizerTest.kt | 60 -------- 6 files changed, 2 insertions(+), 256 deletions(-) delete mode 100644 purchases/src/main/kotlin/com/revenuecat/purchases/common/Anonymizer.kt delete mode 100644 purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizer.kt delete mode 100644 purchases/src/test/java/com/revenuecat/purchases/common/AnonymizerTest.kt delete mode 100644 purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizerTest.kt diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt index 2de554c97c..a85121d84c 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.pm.PackageManager import android.preference.PreferenceManager import androidx.annotation.VisibleForTesting -import com.revenuecat.purchases.common.Anonymizer import com.revenuecat.purchases.common.AppConfig import com.revenuecat.purchases.common.Backend import com.revenuecat.purchases.common.BackendHelper @@ -18,7 +17,6 @@ import com.revenuecat.purchases.common.LogIntent import com.revenuecat.purchases.common.PlatformInfo import com.revenuecat.purchases.common.caching.DeviceCache import com.revenuecat.purchases.common.debugLog -import com.revenuecat.purchases.common.diagnostics.DiagnosticsAnonymizer import com.revenuecat.purchases.common.diagnostics.DiagnosticsFileHelper import com.revenuecat.purchases.common.diagnostics.DiagnosticsHelper import com.revenuecat.purchases.common.diagnostics.DiagnosticsSynchronizer @@ -105,7 +103,6 @@ internal class PurchasesFactory( diagnosticsTracker = DiagnosticsTracker( appConfig, diagnosticsFileHelper, - DiagnosticsAnonymizer(Anonymizer()), diagnosticsHelper, eventsDispatcher, ) diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/common/Anonymizer.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/common/Anonymizer.kt deleted file mode 100644 index 4e6eff4db5..0000000000 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/common/Anonymizer.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.revenuecat.purchases.common - -internal class Anonymizer { - private companion object { - const val EMAIL_REGEX = "[a-zA-Z0-9_!#\$%&'*+/=?`{|}~^.]+@[a-zA-Z0-9]+\\.[a-zA-Z]+" // Based on RFC5322 - const val UUID_REGEX = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" - const val IP_REGEX = "((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}" - - const val REDACTED = "*****" - } - - private val anonymizeRegex = Regex("$EMAIL_REGEX|$UUID_REGEX|$IP_REGEX") - - fun anonymizedString(textToAnonymize: String): String { - return textToAnonymize.replace(anonymizeRegex, REDACTED) - } - - fun anonymizedMap(mapToAnonymize: Map): Map { - return mapToAnonymize.mapValues { (_, value) -> anonymizedAny(value) } - } - - fun anonymizedStringMap(mapToAnonymize: Map): Map { - return mapToAnonymize.mapValues { (_, value) -> anonymizedString(value) } - } - - private fun anonymizedAny(valueToAnonymize: Any): Any { - return when (valueToAnonymize) { - is String -> anonymizedString(valueToAnonymize) - is List<*> -> valueToAnonymize.map { if (it == null) null else anonymizedAny(it) } - is Map<*, *> -> valueToAnonymize.mapValues { (_, value) -> - if (value == null) null else anonymizedAny(value) - } - else -> valueToAnonymize - } - } -} diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizer.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizer.kt deleted file mode 100644 index f1cf874fa0..0000000000 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizer.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.revenuecat.purchases.common.diagnostics - -import com.revenuecat.purchases.common.Anonymizer - -internal class DiagnosticsAnonymizer( - private val anonymizer: Anonymizer, -) { - fun anonymizeEntryIfNeeded(diagnosticsEntry: DiagnosticsEntry): DiagnosticsEntry { - return diagnosticsEntry.copy( - properties = anonymizer.anonymizedMap(diagnosticsEntry.properties), - ) - } -} diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsTracker.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsTracker.kt index cfee837aee..21a77bc531 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsTracker.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/common/diagnostics/DiagnosticsTracker.kt @@ -24,7 +24,6 @@ import kotlin.time.Duration internal class DiagnosticsTracker( private val appConfig: AppConfig, private val diagnosticsFileHelper: DiagnosticsFileHelper, - private val diagnosticsAnonymizer: DiagnosticsAnonymizer, private val diagnosticsHelper: DiagnosticsHelper, private val diagnosticsDispatcher: Dispatcher, ) { @@ -260,10 +259,9 @@ internal class DiagnosticsTracker( internal fun trackEventInCurrentThread(diagnosticsEntry: DiagnosticsEntry) { if (isAndroidNOrNewer()) { - val anonymizedEvent = diagnosticsAnonymizer.anonymizeEntryIfNeeded(diagnosticsEntry) - verboseLog("Tracking diagnostics entry: $anonymizedEvent") + verboseLog("Tracking diagnostics entry: $diagnosticsEntry") try { - diagnosticsFileHelper.appendEvent(anonymizedEvent) + diagnosticsFileHelper.appendEvent(diagnosticsEntry) } catch (e: IOException) { verboseLog("Error tracking diagnostics entry: $e") } diff --git a/purchases/src/test/java/com/revenuecat/purchases/common/AnonymizerTest.kt b/purchases/src/test/java/com/revenuecat/purchases/common/AnonymizerTest.kt deleted file mode 100644 index bc1ea27908..0000000000 --- a/purchases/src/test/java/com/revenuecat/purchases/common/AnonymizerTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -package com.revenuecat.purchases.common - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config(manifest = Config.NONE) -class AnonymizerTest { - private lateinit var anonymizer: Anonymizer - - @Before - fun setup() { - anonymizer = Anonymizer() - } - - // region anonymizeString - - @Test - fun `anonymizeString removes emails`() { - val originalString = "Some random text with an sample.123+34@revenuecat.com and test.1@gmail.com email." - val expectedString = "Some random text with an ***** and ***** email." - assertThat(anonymizer.anonymizedString(originalString)).isEqualTo(expectedString) - } - - @Test - fun `anonymizeString removes UUIDs`() { - val originalString = "Some random text with a 2c5e8760-a864-11ed-afa1-0242ac120002 uuid." - val expectedString = "Some random text with a ***** uuid." - assertThat(anonymizer.anonymizedString(originalString)).isEqualTo(expectedString) - } - - @Test - fun `anonymizeString removes IPs`() { - val originalString = "Some random text with a 192.168.1.1 ip." - val expectedString = "Some random text with a ***** ip." - assertThat(anonymizer.anonymizedString(originalString)).isEqualTo(expectedString) - } - - @Test - fun `anonymizeString removes multiple pieces of PII`() { - val originalString = "Some random text with a 685a5091-7e0b-44c0-a948-61ce324477c4 uuid and a " + - "random.test@revenuecat.com email and a random 1.1.1.1 ip" - val expectedString = "Some random text with a ***** uuid and a ***** email and a random ***** ip" - assertThat(anonymizer.anonymizedString(originalString)).isEqualTo(expectedString) - } - - // endregion - - // region anonymizeStringMap - - @Test - fun `anonymizedStringMap anonymizes string fields if needed`() { - val originalMap = mapOf( - "key-1" to "string with some.pii@revenuecat.com and 192.168.1.1.", - "key-2" to "string without pii", - "key-3" to "string with other.pii@revenuecat.com" - ) - val expectedMap = mapOf( - "key-1" to "string with ***** and *****.", - "key-2" to "string without pii", - "key-3" to "string with *****" - ) - assertThat(anonymizer.anonymizedStringMap(originalMap)).isEqualTo(expectedMap) - } - - // endregion - - // region anonymizedMap - - @Test - fun `anonymizedMap anonymizes all string fields if needed`() { - val originalMap = mapOf( - "key-1" to 1234, - "key-2" to "string with some.pii@revenuecat.com and 192.168.1.1.", - "key-3" to true, - "key-4" to "string without pii", - "key-5" to "string with other.pii@revenuecat.com" - ) - val expectedMap = mapOf( - "key-1" to 1234, - "key-2" to "string with ***** and *****.", - "key-3" to true, - "key-4" to "string without pii", - "key-5" to "string with *****" - ) - assertThat(anonymizer.anonymizedMap(originalMap)).isEqualTo(expectedMap) - } - - @Test - fun `anonymizedMap anonymizes lists`() { - val originalMap = mapOf( - "key-1" to 1234, - "key-2" to "string with some.pii@revenuecat.com and 192.168.1.1.", - "key-3" to listOf("some string with some.pii@revenuecat.com", "and some without", 1234), - "key-4" to "string without pii", - "key-5" to "string with other.pii@revenuecat.com" - ) - val expectedMap = mapOf( - "key-1" to 1234, - "key-2" to "string with ***** and *****.", - "key-3" to listOf("some string with *****", "and some without", 1234), - "key-4" to "string without pii", - "key-5" to "string with *****" - ) - assertThat(anonymizer.anonymizedMap(originalMap)).isEqualTo(expectedMap) - } - - @Test - fun `anonymizedMap anonymizes nested maps`() { - val originalMap = mapOf( - "key-1" to 1234, - "key-2" to "string with some.pii@revenuecat.com and 192.168.1.1.", - "key-3" to mapOf( - "nested-key-1" to "some random.email@revenuecat.com", - "nested-key-2" to 4321, - "nested-key-3" to "string without PII" - ), - "key-4" to "string without pii", - "key-5" to "string with other.pii@revenuecat.com" - ) - val expectedMap = mapOf( - "key-1" to 1234, - "key-2" to "string with ***** and *****.", - "key-3" to mapOf( - "nested-key-1" to "some *****", - "nested-key-2" to 4321, - "nested-key-3" to "string without PII" - ), - "key-4" to "string without pii", - "key-5" to "string with *****" - ) - assertThat(anonymizer.anonymizedMap(originalMap)).isEqualTo(expectedMap) - } - - // endregion -} diff --git a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizerTest.kt b/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizerTest.kt deleted file mode 100644 index dfed5c40a9..0000000000 --- a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsAnonymizerTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.revenuecat.purchases.common.diagnostics - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.revenuecat.purchases.common.Anonymizer -import com.revenuecat.purchases.common.DateProvider -import io.mockk.every -import io.mockk.mockk -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config -import java.util.Date - -@RunWith(AndroidJUnit4::class) -@Config(manifest = Config.NONE) -class DiagnosticsAnonymizerTest { - - private val testDate = Date(1675954145L) // Thursday, February 9, 2023 2:49:05 PM GMT - - private lateinit var anonymizer: Anonymizer - - private lateinit var diagnosticsAnonymizer: DiagnosticsAnonymizer - - private lateinit var testDateProvider: DateProvider - - @Before - fun setup() { - testDateProvider = object : DateProvider { - override val now: Date - get() = testDate - } - - anonymizer = mockk() - - diagnosticsAnonymizer = DiagnosticsAnonymizer(anonymizer) - } - - @Test - fun `anonymizeEntryIfNeeded anonymizes event properties`() { - val originalPropertiesMap = mapOf("key-1" to "value-1") - val expectedPropertiesMap = mapOf("key-1" to "anonymized-value-1") - val eventToAnonymize = DiagnosticsEntry( - name = DiagnosticsEntryName.HTTP_REQUEST_PERFORMED, - properties = originalPropertiesMap, - dateProvider = testDateProvider - ) - val expectedEvent = DiagnosticsEntry( - name = DiagnosticsEntryName.HTTP_REQUEST_PERFORMED, - properties = expectedPropertiesMap, - dateProvider = testDateProvider, - dateTime = testDate - ) - every { - anonymizer.anonymizedMap(originalPropertiesMap) - } returns expectedPropertiesMap - val anonymizedEvent = diagnosticsAnonymizer.anonymizeEntryIfNeeded(eventToAnonymize) - assertThat(anonymizedEvent).isEqualTo(expectedEvent) - } -} From 36d4c3c8ad889742fa24e9ca56cf9c982c998227 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Fri, 26 Jul 2024 15:25:38 +0200 Subject: [PATCH 2/3] Fix tests --- .../common/diagnostics/DiagnosticsTrackerFunctionalTest.kt | 2 -- .../purchases/common/diagnostics/DiagnosticsTrackerTest.kt | 4 ---- 2 files changed, 6 deletions(-) diff --git a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerFunctionalTest.kt b/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerFunctionalTest.kt index eed4122a7a..6a376c2ccf 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerFunctionalTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerFunctionalTest.kt @@ -2,7 +2,6 @@ package com.revenuecat.purchases.common.diagnostics import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.revenuecat.purchases.common.Anonymizer import com.revenuecat.purchases.common.FileHelper import com.revenuecat.purchases.common.SyncDispatcher import io.mockk.every @@ -41,7 +40,6 @@ class DiagnosticsTrackerFunctionalTest { diagnosticsTracker = DiagnosticsTracker( appConfig = mockk(), diagnosticsFileHelper = diagnosticsFileHelper, - diagnosticsAnonymizer = DiagnosticsAnonymizer(Anonymizer()), diagnosticsHelper = DiagnosticsHelper(applicationContext, diagnosticsFileHelper, lazy { mockk(relaxed = true) }), diagnosticsDispatcher = SyncDispatcher(), ) diff --git a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerTest.kt b/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerTest.kt index 726e0224c5..44abd35999 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/common/diagnostics/DiagnosticsTrackerTest.kt @@ -5,13 +5,11 @@ import android.content.SharedPreferences import androidx.test.ext.junit.runners.AndroidJUnit4 import com.revenuecat.purchases.CustomerInfo import com.revenuecat.purchases.EntitlementInfos -import com.revenuecat.purchases.PurchasesAreCompletedBy import com.revenuecat.purchases.PurchasesAreCompletedBy.MY_APP import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.PurchasesErrorCode import com.revenuecat.purchases.Store import com.revenuecat.purchases.VerificationResult -import com.revenuecat.purchases.common.Anonymizer import com.revenuecat.purchases.common.AppConfig import com.revenuecat.purchases.common.Dispatcher import com.revenuecat.purchases.common.PlatformInfo @@ -28,7 +26,6 @@ import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic import io.mockk.verify -import io.mockk.verifyOrder import io.mockk.verifySequence import org.junit.After import org.junit.Before @@ -82,7 +79,6 @@ class DiagnosticsTrackerTest { diagnosticsTracker = DiagnosticsTracker( appConfig, diagnosticsFileHelper, - DiagnosticsAnonymizer(Anonymizer()), DiagnosticsHelper(mockk(), diagnosticsFileHelper, lazy { sharedPreferences }), dispatcher ) From f33f0e6175887b68fa2340e92ac1fe4b9deb7bf4 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Mon, 29 Jul 2024 11:21:36 +0200 Subject: [PATCH 3/3] Fix docs to better reflect the implementation --- .../kotlin/com/revenuecat/purchases/PurchasesConfiguration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesConfiguration.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesConfiguration.kt index 2029c78583..9bce868577 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesConfiguration.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesConfiguration.kt @@ -157,7 +157,7 @@ open class PurchasesConfiguration(builder: Builder) { /** * Enabling diagnostics will send some performance and debugging information from the SDK to our servers. * Examples of this information include response times, cache hits or error codes. - * This information will be anonymized so it can't be traced back to the end-user. + * No personal identifiable information will be collected. * The default value is false. * * Diagnostics is only available in Android API 24+