From 754eefd8aa4d3e73e5669a3ffdd5c9fed24eb7bb Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Tue, 11 Oct 2022 14:31:55 +0200 Subject: [PATCH] RUMM-2651 SR - skip new lines and spaces when obfuscating texts --- detekt.yml | 1 + .../mapper/MaskAllTextWireframeMapper.kt | 9 +- .../recorder/mapper/StringObfuscator.kt | 26 ++++ .../mapper/BaseTextViewWireframeMapperTest.kt | 11 +- .../MaskAllTextViewWireframeMapperTest.kt | 28 ++-- .../recorder/mapper/StringObfuscatorTest.kt | 145 ++++++++++++++++++ 6 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/StringObfuscator.kt create mode 100644 library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/StringObfuscatorTest.kt diff --git a/detekt.yml b/detekt.yml index 59355e9f43..65723f6859 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1170,6 +1170,7 @@ datadog: - "kotlin.Byte.toInt()" - "kotlin.ByteArray.constructor(kotlin.Int)" - "kotlin.Char.isLowerCase()" + - "kotlin.Char.isWhitespace()" - "kotlin.Char.titlecase(java.util.Locale)" - "kotlin.CharArray.constructor(kotlin.Int, kotlin.Function1)" - "kotlin.Double.isNaN()" diff --git a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextWireframeMapper.kt b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextWireframeMapper.kt index 932507868c..d3a4ef1451 100644 --- a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextWireframeMapper.kt +++ b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextWireframeMapper.kt @@ -9,14 +9,11 @@ package com.datadog.android.sessionreplay.recorder.mapper import android.widget.TextView internal class MaskAllTextWireframeMapper( - viewWireframeMapper: ViewWireframeMapper = ViewWireframeMapper() + viewWireframeMapper: ViewWireframeMapper = ViewWireframeMapper(), + private val stringObfuscator: StringObfuscator = StringObfuscator() ) : TextWireframeMapper(viewWireframeMapper) { override fun resolveTextValue(textView: TextView): String { - return String(CharArray(textView.text.length) { CHARACTER_MASK }) - } - - companion object { - private const val CHARACTER_MASK = 'x' + return stringObfuscator.obfuscate(textView.text.toString()) } } diff --git a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/StringObfuscator.kt b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/StringObfuscator.kt new file mode 100644 index 0000000000..432d56506a --- /dev/null +++ b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/StringObfuscator.kt @@ -0,0 +1,26 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.recorder.mapper + +internal class StringObfuscator { + fun obfuscate(stringValue: String): String { + return String( + CharArray(stringValue.length) { + val character = stringValue[it] + if (character.isWhitespace()) { + character + } else { + CHARACTER_MASK + } + } + ) + } + + companion object { + private const val CHARACTER_MASK = 'x' + } +} diff --git a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/BaseTextViewWireframeMapperTest.kt b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/BaseTextViewWireframeMapperTest.kt index 800ef823ad..e601014aca 100644 --- a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/BaseTextViewWireframeMapperTest.kt +++ b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/BaseTextViewWireframeMapperTest.kt @@ -15,6 +15,7 @@ import com.datadog.android.sessionreplay.recorder.densityNormalized import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.StringForgery import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -25,6 +26,9 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes lateinit var testedTextWireframeMapper: TextWireframeMapper + @StringForgery + lateinit var fakeText: String + @BeforeEach fun `set up`() { testedTextWireframeMapper = initTestedMapper() @@ -44,7 +48,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes // Given val fakeFontSize = forge.aFloat(min = 0f) val fakeStyleColor = forge.aStringMatching("#[0-9a-f]{6}ff") - val fakeText = forge.aString() val fakeFontColor = fakeStyleColor .substring(1) .toLong(16) @@ -81,7 +84,7 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes ) { // Given val mockTextView: TextView = forge.aMockView().apply { - whenever(this.text).thenReturn(forge.aString()) + whenever(this.text).thenReturn(fakeText) whenever(this.typeface).thenReturn(mock()) whenever(this.textAlignment).thenReturn(fakeTextAlignment) } @@ -108,7 +111,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes forge: Forge ) { // Given - val fakeText = forge.aString() val mockTextView: TextView = forge.aMockView().apply { whenever(this.text).thenReturn(fakeText) whenever(this.typeface).thenReturn(mock()) @@ -133,7 +135,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes @Test fun `M resolve a TextWireframe W map() { TextView with textPadding }`(forge: Forge) { // Given - val fakeText = forge.aString() val fakeTextPaddingTop = forge.anInt() val fakeTextPaddingBottom = forge.anInt() val fakeTextPaddingStart = forge.anInt() @@ -192,7 +193,7 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes } val mockTextView = forge.aMockView().apply { whenever(this.background).thenReturn(mockDrawable) - whenever(this.text).thenReturn(forge.aString()) + whenever(this.text).thenReturn(fakeText) whenever(this.typeface).thenReturn(mock()) } diff --git a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextViewWireframeMapperTest.kt b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextViewWireframeMapperTest.kt index a024a22232..9099af4e3e 100644 --- a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextViewWireframeMapperTest.kt +++ b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/mapper/MaskAllTextViewWireframeMapperTest.kt @@ -6,7 +6,6 @@ package com.datadog.android.sessionreplay.recorder.mapper -import android.widget.Button import android.widget.TextView import com.datadog.android.sessionreplay.recorder.aMockView import com.datadog.android.sessionreplay.utils.ForgeConfigurator @@ -14,12 +13,14 @@ import com.datadog.tools.unit.extensions.ApiLevelExtension import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.quality.Strictness @@ -33,14 +34,21 @@ import org.mockito.quality.Strictness @ForgeConfiguration(ForgeConfigurator::class) internal class MaskAllTextViewWireframeMapperTest : BaseTextViewWireframeMapperTest() { + @Mock + lateinit var mockStringObfuscator: StringObfuscator + + @StringForgery + lateinit var fakeMaskedStringValue: String + // region super override fun initTestedMapper(): TextWireframeMapper { - return MaskAllTextWireframeMapper() + whenever(mockStringObfuscator.obfuscate(fakeText)).thenReturn(fakeMaskedStringValue) + return MaskAllTextWireframeMapper(stringObfuscator = mockStringObfuscator) } override fun resolveTextValue(textView: TextView): String { - return String(CharArray(textView.text.length) { 'x' }) + return fakeMaskedStringValue } // endregion @@ -48,19 +56,21 @@ internal class MaskAllTextViewWireframeMapperTest : BaseTextViewWireframeMapperT // region Unit tests @Test - fun `M resolve a TextWireframe with masked text W map() { TextView with text }`(forge: Forge) { + fun `M resolve a TextWireframe with masked text W map(){TextView}`( + forge: Forge + ) { // Given - val fakeText = forge.aString { 'x' } - val mockButton: TextView = forge.aMockView