Skip to content

Commit

Permalink
feat: mask payment info (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohnoor94 authored Oct 8, 2023
1 parent 118b62f commit 9e3c81f
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,22 @@ import com.expediagroup.sdk.core.constant.HeaderKey.AUTHORIZATION

internal object LogMaskingRegex {
val AUTHORIZATION_REGEX: Regex = "(?<=$AUTHORIZATION: ($EAN|$BASIC|$BEARER|)\\s)\\S+".toRegex()

private val paymentKeys =
arrayListOf(
"security_code",
"card_number",
"card_cvv_response",
"card_avs_response",
"pin",
"card_cvv",
"account_number",
"card_cvv2",
"card_cvv2_response",
"cvv"
)

val PAYMENT_REGEX = "(?<=[\"']?(${paymentKeys.joinToString("|")})[\"']?:\\s?[\"'])(\\s*[^\"']+\\s*)(?=[\"'])".toRegex()

val NUMBER_FIELD_REGEX = "(?<=[\"']?number[\"']?:\\s?[\"'])(\\s*\\d{15,16}\\s*)(?=[\"'])".toRegex()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,32 @@
package com.expediagroup.sdk.core.plugin.logging

import com.expediagroup.sdk.core.constant.LogMaskingRegex.AUTHORIZATION_REGEX
import com.expediagroup.sdk.core.constant.LogMaskingRegex.NUMBER_FIELD_REGEX
import com.expediagroup.sdk.core.constant.LogMaskingRegex.PAYMENT_REGEX
import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED

internal fun mask(message: String): String = MaskProvider.masks.fold(message) { acc, mask -> mask.mask(acc) }

internal interface Mask {
val regex: Regex

fun mask(string: String): String = string.replace(regex) { maskSubstring(it.value) }

fun maskSubstring(string: String) = OMITTED
}

internal object MaskProvider {
val masks = listOf<Mask>(AuthMask)
val masks = listOf(AuthMask, PaymentMask, PaymentNumberMask)

object AuthMask : Mask {
override val regex: Regex = AUTHORIZATION_REGEX

override fun maskSubstring(string: String) = OMITTED
}
}

internal interface Mask {
val regex: Regex

fun mask(string: String): String = string.replace(regex) { maskSubstring(it.value) }
object PaymentMask : Mask {
override val regex: Regex = PAYMENT_REGEX
}

fun maskSubstring(string: String): String
object PaymentNumberMask : Mask {
override val regex: Regex = NUMBER_FIELD_REGEX
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,26 @@ import com.expediagroup.sdk.core.constant.Authentication.EAN
import com.expediagroup.sdk.core.constant.HeaderKey.AUTHORIZATION
import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED
import com.expediagroup.sdk.core.plugin.logging.MaskProvider.AuthMask
import com.expediagroup.sdk.core.plugin.logging.MaskProvider.PaymentMask
import com.expediagroup.sdk.core.plugin.logging.MaskProvider.PaymentNumberMask
import io.mockk.mockkObject
import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

internal class LogMaskerTest {
@ParameterizedTest
@ValueSource(strings = [BASIC, BEARER, EAN])
fun `given text apply all masks available`(authType: String) {
val text = "$AUTHORIZATION: $authType token"
val text = "$AUTHORIZATION: $authType token \"number\":\"4111111111111111\",'security_code':'123',\"something else\""
mockkObject(AuthMask)

val maskedText = mask(text)

assertThat(maskedText).isEqualTo("$AUTHORIZATION: $authType $OMITTED")
assertThat(maskedText).isEqualTo("$AUTHORIZATION: $authType $OMITTED \"number\":\"$OMITTED\",'security_code':'$OMITTED',\"something else\"")
verify(exactly = 1) { AuthMask.mask(text) }
}

Expand All @@ -53,4 +56,57 @@ internal class LogMaskerTest {
assertThat(maskedText).isEqualTo("$AUTHORIZATION: $authType $OMITTED")
}
}

@Nested
inner class PaymentMaskTest {
@ParameterizedTest
@ValueSource(
strings = [
"security_code",
"card_number",
"card_cvv_response",
"card_avs_response",
"pin",
"card_cvv",
"account_number",
"card_cvv2",
"card_cvv2_response",
"cvv"
]
)
fun `given a text with payment info then omit it`(key: String) {
assertThat(PaymentMask.mask("$key:\"123456\" something else")).isEqualTo("$key:\"$OMITTED\" something else")
assertThat(PaymentMask.mask("\"$key\":\"123456\" something else")).isEqualTo("\"$key\":\"$OMITTED\" something else")
assertThat(PaymentMask.mask("$key: \"123456\" something else")).isEqualTo("$key: \"$OMITTED\" something else")
assertThat(PaymentMask.mask("$key:'123456' something else")).isEqualTo("$key:'$OMITTED' something else")
assertThat(PaymentMask.mask("$key: '123456' something else")).isEqualTo("$key: '$OMITTED' something else")
}
}

@Nested
inner class PaymentNumberTest {
@ParameterizedTest
@ValueSource(
strings = [
"012345678901234",
"0123456789012345",
"4111111111111111"
]
)
fun `given a text with number of 15 or 16 digits then omit it`(number: String) {
assertThat(PaymentNumberMask.mask("number:\"$number\" something else")).isEqualTo("number:\"$OMITTED\" something else")
assertThat(PaymentNumberMask.mask("number: \"$number\" something else")).isEqualTo("number: \"$OMITTED\" something else")
assertThat(PaymentNumberMask.mask("number:'$number' something else")).isEqualTo("number:'$OMITTED' something else")
assertThat(PaymentNumberMask.mask("number: '$number' something else")).isEqualTo("number: '$OMITTED' something else")
}

@Test
fun `given a text with number of 14 digits then do not omit it`() {
val number = "01234567890123"
assertThat(PaymentNumberMask.mask("number:\"$number\" something else")).isEqualTo("number:\"$number\" something else")
assertThat(PaymentNumberMask.mask("number: \"$number\" something else")).isEqualTo("number: \"$number\" something else")
assertThat(PaymentNumberMask.mask("number:'$number' something else")).isEqualTo("number:'$number' something else")
assertThat(PaymentNumberMask.mask("number: '$number' something else")).isEqualTo("number: '$number' something else")
}
}
}

0 comments on commit 9e3c81f

Please sign in to comment.