From 2a9b412d618391c158345b06297c18cb230a4508 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sun, 22 Dec 2024 13:36:58 +0300 Subject: [PATCH 01/10] feat: add log masking feature --- .../expediagroup/sdk/core/common/Feature.kt | 5 + .../core/logging/masking/JsonFieldFilter.kt | 19 -- .../masking/JsonFieldPatternBuilder.kt | 17 -- .../sdk/core/logging/masking/LogMasker.kt | 91 +++++++ .../core/logging/masking/LogMaskingFeature.kt | 33 +++ .../sdk/core/logging/masking/MaskLogsUtils.kt | 100 -------- .../logging/masking/MaskingPatternBuilder.kt | 79 ++++++ .../core/logging/masking/PatternBuilders.kt | 46 ++++ .../common/DefaultRequestExecutor.kt | 1 - .../LodgingConnectivityLogMasking.kt | 27 +++ .../payment/PaymentClient.kt | 5 + .../sandbox/SandboxDataManagementClient.kt | 5 + .../supply/reservation/ReservationClient.kt | 5 + ....java => OkHttpClientFeatureJavaTest.java} | 2 +- .../sdk/core/logging/masking/LogMaskerTest.kt | 148 ++++++++++++ .../logging/masking/LogMaskingFeatureTest.kt | 56 +++++ .../masking/MaskingPatternBuilderTest.kt | 225 ++++++++++++++++++ .../logging/masking/PatternBuildersTest.kt | 97 ++++++++ ...tionTest.kt => OkHttpClientFeatureTest.kt} | 4 +- 19 files changed, 825 insertions(+), 140 deletions(-) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt rename code/src/test/java/com/expediagroup/sdk/core/okhttp/{OkHttpClientConfigurationJavaTest.java => OkHttpClientFeatureJavaTest.java} (87%) create mode 100644 code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt create mode 100644 code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt create mode 100644 code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt create mode 100644 code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt rename code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/{OkHttpClientConfigurationTest.kt => OkHttpClientFeatureTest.kt} (95%) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt new file mode 100644 index 00000000..2362f8ac --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt @@ -0,0 +1,5 @@ +package com.expediagroup.sdk.core.common + +internal interface Feature { + fun enable() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt deleted file mode 100644 index b36f1b2a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import com.ebay.ejmask.core.BaseFilter - -/** - * A filter class that extends the BaseFilter to apply masking on specific JSON fields using - * `ExpediaGroupJsonFieldPatternBuilder` for pattern building. - * - * This filter helps in masking sensitive JSON fields by replacing them with a predefined pattern. - * - * @constructor - * Initializes ExpediaGroupJsonFieldFilter with the specified fields to be masked. - * - * @param maskedFields An array of strings representing the names of the fields to be masked. - */ -internal class JsonFieldFilter(maskedFields: Array) : BaseFilter( - JsonFieldPatternBuilder::class.java, - *maskedFields -) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt deleted file mode 100644 index 82543b08..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder -import com.expediagroup.sdk.core.logging.common.Constant.OMITTED - -/** - * A builder class for creating JSON field replacement patterns specifically for Expedia Group. - * - * This class extends the `JsonFieldPatternBuilder` and provides an implementation for building - * replacement patterns for JSON field masking. - * - * The replacement pattern format generated by this builder is structured to conceal sensitive - * data while keeping a specified number of characters visible. - */ -internal class JsonFieldPatternBuilder : JsonFieldPatternBuilder() { - override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = "\"$1$2$OMITTED\"" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt new file mode 100644 index 00000000..8986c858 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt @@ -0,0 +1,91 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.ebay.ejmask.api.MaskingPattern + +/** + * Interface for pattern-based masking. + */ +internal interface PatternBasedMask : (String) -> String { + /** + * Adds masking patterns if they do not already exist. + * + * @param maskingPatterns Vararg of MaskingPattern to be added. + * @return True if patterns were added, false if they already existed. + */ + fun addPatternIfNotExists(vararg maskingPatterns: MaskingPattern): Boolean + + /** + * Retrieves the list of current masking patterns. + * + * @return A list of MaskingPattern. + */ + fun patterns(): List + + /** + * Clears all existing masking patterns. + */ + fun clear() +} + +/** + * Object implementing the PatternBasedMask interface. + */ +internal val mask = object : PatternBasedMask { + val patterns: MutableList = mutableListOf() + + /** + * Applies all masking patterns to the input string. + * + * @param input The input string to be masked. + * @return The masked string. + */ + override fun invoke(input: String): String { + var masked = input + + patterns.forEach { pattern: MaskingPattern -> + masked = pattern.replaceAll(masked) + } + + return masked + } + + /** + * Retrieves the list of current masking patterns. + * + * @return A list of MaskingPattern. + */ + override fun patterns() = + this.patterns.toList() + + /** + * Clears all existing masking patterns. + */ + override fun clear() { + patterns.clear() + } + + /** + * Adds masking patterns if they do not already exist. + * + * @param maskingPatterns Vararg of MaskingPattern to be added. + * @return True if patterns were added, false if they already existed. + */ + override fun addPatternIfNotExists(vararg maskingPatterns: MaskingPattern): Boolean { + var addedPattern = false + + maskingPatterns.forEach newPatterns@{ pattern -> + patterns.forEach existingPatterns@{ + val patternExists = (it.pattern.pattern() == pattern.pattern.pattern()) + .and(it.replacement == pattern.replacement) + + if (patternExists) { + return@newPatterns + } + } + + patterns.add(pattern).also { addedPattern = true } + } + + return addedPattern + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt new file mode 100644 index 00000000..1f05f60d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt @@ -0,0 +1,33 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.expediagroup.sdk.core.common.Feature + +/** + * Interface representing the configuration for masking. + */ +internal abstract class LogMaskingFeature: Feature { + /** + * A set of globally masked fields. + * + * @return A set of strings representing the global masked fields. + */ + open val globalMaskedFields: Set + get() = emptySet() + + /** + * A set of path-specific masked fields. + * + * @return A set of lists of strings representing the path masked fields. + */ + open val pathMaskedFields: Set> + get() = emptySet() + + override fun enable() { + mask.addPatternIfNotExists( + *MaskingPatternBuilder().apply { + globalFields(*globalMaskedFields.toTypedArray()) + pathFields(*pathMaskedFields.toTypedArray()) + }.build().toTypedArray() + ) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt deleted file mode 100644 index eb2dfd51..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import com.ebay.ejmask.core.BaseFilter -import com.ebay.ejmask.core.EJMask -import com.ebay.ejmask.core.EJMaskInitializer -import com.ebay.ejmask.core.util.LoggerUtil - -/** - * Masks sensitive information within the provided log string. - * - * @param logs The log string that may contain sensitive information requiring masking. - * @return A new log string with sensitive information masked. - */ -fun maskLogs(logs: String): String { - return MaskLogs.execute(logs) -} - -/** - * Configures log masking by adding specified fields to the mask list. - * - * This function integrates with the log masking system to include additional fields - * that should be masked in the logs. The fields provided in the parameter are added - * to the set of fields that will have their values masked when logs are generated. - * - * @param fields The set of field names that need to be masked in the logs. - */ -fun configureLogMasking(fields: Set) { - MaskLogs.addFields(fields) -} - -/** - * Checks if a specified field is among the fields that should be masked. - * - * @param field The name of the field to check. - * @return `true` if the field should be masked, `false` otherwise. - */ -fun isMaskedField(field: String): Boolean { - return MaskLogs.maskedFields.contains(field) -} - -/** - * A utility class for masking sensitive information in log strings. - * - * The `MaskLogs` class is designed to replace sensitive information within logs with masked values. - * The class implements the `Function1` interface, enabling it to be invoked with a log string - * to produce a masked version of the string. - * - * The masking process relies on predefined filters that determine which fields within the log - * should be masked. Filters can be added and configured using the companion object's methods. - */ -private class MaskLogs : (String) -> String { - companion object { - @JvmStatic - val filters: MutableList = mutableListOf() - - val maskedFields: MutableSet = mutableSetOf() - - @JvmStatic - val INSTANCE = MaskLogs() - - /** - * Executes the masking process on the provided log string. - * - * @param logs The log string that may contain sensitive information requiring masking. - */ - @JvmStatic - fun execute(logs: String) = INSTANCE(logs) - - /** - * Adds specified fields to the list of fields to be masked in logs. - * - * The fields provided in the parameter are added to the internal set of fields - * and corresponding filters are created and added to the filter list. These filters - * are then integrated into the masking system to ensure the specified fields are - * masked in any logs they appear in. - * - * @param fields The set of field names that need to be masked in the logs. - */ - @JvmStatic - fun addFields(fields: Set) { - maskedFields.addAll(fields) - filters.add(JsonFieldFilter(fields.toTypedArray())) - filters.forEach { EJMaskInitializer.addFilter(it) } - } - } - - init { - LoggerUtil.register { _, _, _ -> /* disable logging */ } - } - - /** - * Masks the given text using the EJMask utility. - * - * @param text The input text that needs to be masked. - * @return The masked version of the input text. - */ - override fun invoke(text: String): String { - return EJMask.mask(text) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt new file mode 100644 index 00000000..ff5b4bf8 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt @@ -0,0 +1,79 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.ebay.ejmask.api.MaskingPattern + +/** + * Builder class for creating masking patterns. + */ +internal class MaskingPatternBuilder { + private var globalFields: Set = setOf() + private var pathFields: Set> = setOf() + + /** + * Adds global fields to be masked. + * + * @param name Vararg of field names to be masked globally. + * @return The current instance of MaskingPatternBuilder. + */ + fun globalFields(vararg name: String): MaskingPatternBuilder = apply { + globalFields += name.toSortedSet() + } + + /** + * Adds path-specific fields to be masked. + * + * @param paths Vararg of lists of field names to be masked by path. + * @return The current instance of MaskingPatternBuilder. + */ + fun pathFields(vararg paths: List) = apply { + pathFields += paths.map { it.takeLast(2) } + } + + /** + * Builds the list of MaskingPattern based on the added global and path fields. + * + * @return A list of MaskingPattern. + */ + fun build(): List = buildList { + /** + * Builds masking patterns for global fields. + * + * @return A list of MaskingPattern for global fields. + */ + fun buildGlobalFieldsMaskingPattern(): List = + if (globalFields.isEmpty()) { + emptyList() + } else { + val patternGenerator = CustomJsonFullValuePatternBuilder() + listOf( + MaskingPattern( + 0, + patternGenerator.buildPattern(0, *globalFields.toTypedArray()), + patternGenerator.buildReplacement(0, *globalFields.toTypedArray()) + ) + ) + } + + /** + * Builds masking patterns for path-specific fields. + * + * @return A list of MaskingPattern for path-specific fields. + */ + fun buildPathFieldsMaskingPattern(): List = buildList { + pathFields.forEachIndexed { index, path -> + CustomJsonRelativeFieldPatternBuilder().also { patternGenerator -> + add( + MaskingPattern( + index + 1, + patternGenerator.buildPattern(1, *path.toTypedArray()), + patternGenerator.buildReplacement(1, *path.toTypedArray()) + ) + ) + } + } + } + + addAll(buildGlobalFieldsMaskingPattern()) + addAll(buildPathFieldsMaskingPattern()) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt new file mode 100644 index 00000000..6afa933d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt @@ -0,0 +1,46 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.ebay.ejmask.extenstion.builder.json.JsonFullValuePatternBuilder +import com.ebay.ejmask.extenstion.builder.json.JsonRelativeFieldPatternBuilder +import com.expediagroup.sdk.core.logging.common.Constant.OMITTED + + +/** + * Custom implementation of JsonFieldPatternBuilder for building JSON field patterns. + */ +internal class CustomJsonFullValuePatternBuilder : JsonFullValuePatternBuilder() { + + companion object { + @JvmStatic + val REPLACEMENT_TEMPLATE = "\"$1$2$OMITTED\"" + } + + /** + * Builds the replacement string for the given field names. + * Ignores the visibleCharacters parameter. + * + * @param visibleCharacters Number of visible characters. Value is ignored. + * @param fieldNames Vararg of field names. + * @return The replacement string. + */ + override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = + REPLACEMENT_TEMPLATE +} + +internal class CustomJsonRelativeFieldPatternBuilder : JsonRelativeFieldPatternBuilder() { + companion object { + @JvmStatic + val REPLACEMENT_TEMPLATE = "$1$OMITTED$3" + } + + /** + * Builds the replacement string for the given field names. + * Ignores the visibleCharacters parameter. + * + * @param visibleCharacters Number of visible characters. Value is ignored. + * @param fieldNames Vararg of field names. + * @return The replacement string. + */ + override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = + REPLACEMENT_TEMPLATE +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt index 0562c8f4..3075513b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt @@ -50,4 +50,3 @@ class DefaultRequestExecutor( return chainExecutor.proceed(request) } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt new file mode 100644 index 00000000..25a0f422 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt @@ -0,0 +1,27 @@ +package com.expediagroup.sdk.lodgingconnectivity.configuration + +import com.expediagroup.sdk.core.logging.masking.LogMaskingFeature + +/** + * Object implementing the MaskingConfiguration interface. + * Initializes and adds masking patterns based on global and path-specific fields at startup time automatically. + */ +internal val LodgingConnectivityLogMasking = + object : LogMaskingFeature() { + /** + * A set of globally masked fields. + * + * @return A set of strings representing the global masked fields. + */ + override val globalMaskedFields: Set = setOf( + "cvv", + "cvv2", + ) + + /** + * A set of path-specific masked fields. + * + * @return A set of lists of strings representing the path masked fields. + */ + override val pathMaskedFields: Set> = setOf() + } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt index 3a6a75eb..b85f9106 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -23,6 +23,7 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.PaymentApiEndpointProvider import com.expediagroup.sdk.lodgingconnectivity.payment.operation.GetPaymentInstrumentResponse import com.expediagroup.sdk.lodgingconnectivity.payment.operation.PaymentInstrumentQuery @@ -38,6 +39,10 @@ import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInst * or timeouts. */ class PaymentClient(config: ClientConfiguration) : GraphQLClient() { + init { + LodgingConnectivityLogMasking.enable() + } + override val apiEndpoint = PaymentApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.PROD) override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt index 59091baa..afbaf09e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -23,6 +23,7 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.SandboxApiEndpointProvider import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput @@ -75,6 +76,10 @@ import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator.Sa * or timeouts. */ class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() { + init { + LodgingConnectivityLogMasking.enable() + } + override val apiEndpoint = SandboxApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.SANDBOX_PROD) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt index f33d5d18..8421a99d 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -23,6 +23,7 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.SupplyApiEndpointProvider import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationReconciliationInput @@ -61,6 +62,10 @@ import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream.Reserv */ class ReservationClient(config: ClientConfiguration) : GraphQLClient() { + init { + LodgingConnectivityLogMasking.enable() + } + override val apiEndpoint = SupplyApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.PROD) override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( diff --git a/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java b/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java similarity index 87% rename from code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java rename to code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java index 504b7fef..41f8bd40 100644 --- a/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java +++ b/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -public class OkHttpClientConfigurationJavaTest { +public class OkHttpClientFeatureJavaTest { @Test @DisplayName("should access default builder static method") public void defaultStaticBuilderIsAccessibleFromJava() { diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt new file mode 100644 index 00000000..367add44 --- /dev/null +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt @@ -0,0 +1,148 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.ebay.ejmask.api.MaskingPattern +import com.expediagroup.sdk.core.logging.common.Constant.OMITTED +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class LogMaskerTest { + @AfterEach + fun afterEach() { + mask.clear() + } + + @Nested + inner class FieldLogMaskingFeatureTest { + @Test + fun `adding global fields to the masking pattern builder`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + + assertTrue(patterns.first().pattern.pattern().contains("globalField1")) + assertTrue(patterns.first().pattern.pattern().contains("globalField2")) + } + + @Test + fun `adding path fields to the masking pattern builder`() { + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + assertEquals(pathFields.size, patterns.size) + } + + @Test + fun `adding global and path fields to the masking pattern builder`() { + val globalFields = setOf("globalField1", "globalField2") + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + + val maskingPatterns = MaskingPatternBuilder() + .globalFields(*globalFields.toTypedArray()) + .pathFields(*pathFields.toTypedArray()) + .build() + + assertEquals(pathFields.size + 1, maskingPatterns.size) + } + + @Test + fun `masking patterns are not added to the mask if they already exist`() { + val maskingPatterns = listOf( + MaskingPattern(0, "pattern1", "replacement1"), + MaskingPattern(1, "pattern2", "replacement2") + ) + mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) + + assertEquals(maskingPatterns, mask.patterns()) + + val newMaskingPatterns = listOf( + MaskingPattern(0, "pattern1", "replacement1"), + MaskingPattern(1, "pattern2", "replacement2") + ) + + val addResult = mask.addPatternIfNotExists(*newMaskingPatterns.toTypedArray()) + + assertFalse(addResult) + assertEquals(maskingPatterns, mask.patterns()) + } + + @Test + fun `masking patterns are cleared from the mask`() { + val maskingPatterns = listOf( + MaskingPattern(0, "pattern1", "replacement1"), + MaskingPattern(1, "pattern2", "replacement2") + ) + mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) + + assertTrue(mask.patterns().isNotEmpty()) + assertEquals(maskingPatterns.size, mask.patterns().size) + assertEquals(maskingPatterns, mask.patterns()) + + mask.clear() + + assertTrue(mask.patterns().isEmpty()) + } + } + + @Nested + inner class MaskingBehaviorTest { + @Test + fun `masking patterns are applied to the input string`() { + val maskingPatterns = listOf( + MaskingPattern(0, "pattern1", "replacement1"), + MaskingPattern(1, "pattern2", "replacement2") + ) + + mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) + + val masked = mask( + """ + { + "pattern1": "value1", + "pattern2": "value2" + } + """.trimIndent() + ) + + val expected = """ + { + "replacement1": "value1", + "replacement2": "value2" + } + """.trimIndent() + + assertEquals(expected, masked) + } + + @Test + fun `masks global fields in json payload`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + + val actual = patterns.first().replaceAll( + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + } + } + """.trimIndent() + ) + val expected = """ + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + } + } + """.trimIndent() + + assertEquals(expected, actual) + } + } +} diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt new file mode 100644 index 00000000..c7259959 --- /dev/null +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt @@ -0,0 +1,56 @@ +package com.expediagroup.sdk.core.logging.masking + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class LogMaskingFeatureTest { + @Test + fun `verify globalMaskedFields returns empty set`() { + val maskingConfiguration = object : LogMaskingFeature() {} + assertTrue(maskingConfiguration.globalMaskedFields.isEmpty()) + } + + @Test + fun `verify pathMaskedFields returns empty set`() { + val maskingConfiguration = object : LogMaskingFeature() {} + assertTrue(maskingConfiguration.pathMaskedFields.isEmpty()) + } + + @Test + fun `verify globalMaskedFields is overridable`() { + val maskingConfiguration = object : LogMaskingFeature() { + override val globalMaskedFields: Set + get() = setOf("field1", "field2") + } + + assertEquals(2, maskingConfiguration.globalMaskedFields.size) + assertTrue(maskingConfiguration.globalMaskedFields.contains("field1")) + assertTrue(maskingConfiguration.globalMaskedFields.contains("field2")) + } + + @Test + fun `verify pathMaskedFields is overridable`() { + val maskingConfiguration = object : LogMaskingFeature() { + override val pathMaskedFields: Set> + get() = setOf(listOf("field1", "field2")) + } + + assertEquals(1, maskingConfiguration.pathMaskedFields.size) + assertTrue(maskingConfiguration.pathMaskedFields.contains(listOf("field1", "field2"))) + } + + @Test + fun `verify pathMaskedFields and globalMaskedFields are overridable interchangeably`() { + val maskingConfiguration = object : LogMaskingFeature() { + override val globalMaskedFields: Set + get() = setOf("field1", "field2") + override val pathMaskedFields: Set> + get() = setOf(listOf("field3", "field4")) + } + + assertEquals(maskingConfiguration.globalMaskedFields.size, 2) + } +} diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt new file mode 100644 index 00000000..fdf6fa7e --- /dev/null +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt @@ -0,0 +1,225 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.expediagroup.sdk.core.logging.common.Constant.OMITTED +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class MaskingPatternBuilderTest { + @Nested + inner class PatternGenerationTest { + @Test + fun `generated masking patterns are empty if no fields are passed`() { + assertEquals(0, MaskingPatternBuilder().build().size) + } + + @Test + fun `generated masking patterns size is one when only global fields are passed regardless of their count`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + } + + @Test + fun `passed path fields are truncated to the last two elements`() { + val pathFields = setOf(listOf("first", "second", "third", "fourth")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + + println(patterns.first().pattern.pattern()) + assertFalse(patterns.first().pattern.pattern().contains("first")) + assertFalse(patterns.first().pattern.pattern().contains("second")) + + assertTrue(patterns.first().pattern.pattern().contains("third")) + assertTrue(patterns.first().pattern.pattern().contains("fourth")) + } + + @Test + fun `generated masking patterns size is not affected by pattern duplication caused by truncated patterns`() { + val pathFields = setOf(listOf("second", "third"), listOf("first", "second", "third")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + } + + @Test + fun `generated masking patterns size grows linearly when only path-based fields are passed`() { + val pathFields = setOf(listOf("first", "second"), listOf("first", "second", "third")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + assertEquals(pathFields.size, patterns.size) + } + + @Test + fun `generated masking patterns size equals the number of path-based fields passed +1 for global fields when both are passed`() { + val globalFields = setOf("globalField1", "globalField2") + val pathFields = setOf(listOf("first", "second"), listOf("first", "second", "third")) + + val maskingPatterns = MaskingPatternBuilder() + .globalFields(*globalFields.toTypedArray()) + .pathFields(*pathFields.toTypedArray()) + .build() + + assertEquals(pathFields.size + 1, maskingPatterns.size) + } + + @Test + fun `global path fields are passed to builder and consumed in masking patterns generation`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + + assertTrue(patterns.first().pattern.pattern().contains("globalField1")) + assertTrue(patterns.first().pattern.pattern().contains("globalField2")) + } + + @Test + fun `path fields are passed to builder and consumed in masking patterns generation`() { + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + + assertEquals(pathFields.size, patterns.size) + + + assertAll( + { assertTrue(patterns[0].pattern.pattern().contains("first")) }, + { assertTrue(patterns[0].pattern.pattern().contains("second")) }, + { assertTrue(patterns[1].pattern.pattern().contains("third1")) }, + { assertTrue(patterns[1].pattern.pattern().contains("as")) } + ) + } + } + + @Nested + inner class PatternsBehaviourTest { + @Test + fun `masks a field globally when a global field is passed`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + + assertEquals(1, patterns.size) + + val actual = patterns.first().replaceAll( + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + } + } + """.trimIndent() + ) + val expected = """ + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + } + } + """.trimIndent() + + assertEquals(expected, actual) + } + + @Test + fun `masks a field in a path when a path field is passed`() { + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + + assertEquals(pathFields.size, patterns.size) + + var actual = """ + { + "first": { + "second": "value1" + }, + "first1": { + "second1": { + "third1": { + "as": "value2" + } + } + } + } + """.trimIndent() + + patterns.forEach { actual = it.replaceAll(actual) } + + val expected = """ + { + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } + } + } + } + """.trimIndent() + + assertEquals(expected, actual) + } + + @Test + fun `masks a field globally and in a path when both are passed`() { + val globalFields = setOf("globalField1", "globalField2") + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + + val maskingPatterns = MaskingPatternBuilder() + .globalFields(*globalFields.toTypedArray()) + .pathFields(*pathFields.toTypedArray()) + .build() + + assertEquals(pathFields.size + 1, maskingPatterns.size) + + var actual = """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + }, + "first": { + "second": "value3" + }, + "first1": { + "second1": { + "third1": { + "as": "value4" + } + } + } + } + """.trimIndent() + + maskingPatterns.forEach { actual = it.replaceAll(actual) } + + val expected = """ + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + }, + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } + } + } + } + """.trimIndent() + + assertEquals(expected, actual) + } + } +} diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt new file mode 100644 index 00000000..6afb3206 --- /dev/null +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt @@ -0,0 +1,97 @@ +package com.expediagroup.sdk.core.logging.masking + +import com.ebay.ejmask.extenstion.builder.json.JsonFullValuePatternBuilder +import com.ebay.ejmask.extenstion.builder.json.JsonRelativeFieldPatternBuilder +import com.expediagroup.sdk.core.logging.common.Constant.OMITTED +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class PatternBuildersTest { + @Nested + inner class CustomJsonFullValuePatternBuilderTest { + @Test + fun `is child of JsonFullValuePatternBuilder`() { + val custom = CustomJsonFullValuePatternBuilder() + + assertEquals(JsonFullValuePatternBuilder::class.java, custom::class.java.superclass) + } + + @Test + fun `does not override isGroupable`() { + val original = JsonFullValuePatternBuilder() + val custom = CustomJsonFullValuePatternBuilder() + + assertEquals(original.isGroupable, custom.isGroupable) + } + + @Test + fun `buildReplacement contains omitted keyword`() { + val custom = CustomJsonFullValuePatternBuilder() + + assertTrue(custom.buildReplacement(0, "field").contains(OMITTED)) + } + + @Test + fun `buildReplacement always generates proper replacement template`() { + val custom = CustomJsonFullValuePatternBuilder() + + custom.buildReplacement(0, "field1").also { + assertEquals(CustomJsonFullValuePatternBuilder.REPLACEMENT_TEMPLATE, it) + } + + custom.buildReplacement(0, "field1", "field2").also { + assertEquals(CustomJsonFullValuePatternBuilder.REPLACEMENT_TEMPLATE, it) + } + + custom.buildReplacement(0, "field".repeat(1000)).also { + assertEquals(CustomJsonFullValuePatternBuilder.REPLACEMENT_TEMPLATE, it) + } + } + } + + @Nested + inner class CustomJsonRelativeFieldPatternBuilderTest { + @Test + fun `is child of JsonRelativeFieldPatternBuilder`() { + val custom = CustomJsonRelativeFieldPatternBuilder() + + assertEquals(JsonRelativeFieldPatternBuilder::class.java, custom::class.java.superclass) + } + + @Test + fun `does not override isGroupable`() { + val original = JsonRelativeFieldPatternBuilder() + val custom = CustomJsonRelativeFieldPatternBuilder() + + assertEquals(original.isGroupable, custom.isGroupable) + } + + @Test + fun `buildReplacement contains omitted keyword`() { + val custom = CustomJsonRelativeFieldPatternBuilder() + + assertTrue(custom.buildReplacement(0, "field").contains(OMITTED)) + } + + @Test + fun `buildReplacement always generates proper replacement template`() { + val custom = CustomJsonRelativeFieldPatternBuilder() + + custom.buildReplacement(0, "field1").also { + assertEquals(CustomJsonRelativeFieldPatternBuilder.REPLACEMENT_TEMPLATE, it) + } + + custom.buildReplacement(0, "field1", "field2").also { + assertEquals(CustomJsonRelativeFieldPatternBuilder.REPLACEMENT_TEMPLATE, it) + } + + custom.buildReplacement(0, "field".repeat(1000)).also { + assertEquals(CustomJsonRelativeFieldPatternBuilder.REPLACEMENT_TEMPLATE, it) + } + } + } +} diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt similarity index 95% rename from code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt rename to code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt index bf58d74b..44c5adb3 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt @@ -9,10 +9,10 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -class OkHttpClientConfigurationTest { +class OkHttpClientFeatureTest { @Nested - inner class OkHttpClientConfigurationBuilderTest { + inner class OkHttpClientFeatureBuilderTest { @Test fun `builder should build configuration with all properties set`() { // Given From 95252072ed3887cf872825ba7eb54187914c2c75 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sun, 22 Dec 2024 18:50:12 +0300 Subject: [PATCH 02/10] test: address comments --- ...reJavaTest.java => OkHttpClientConfigurationJavaTest.java} | 2 +- ...pClientFeatureTest.kt => OkHttpClientConfigurationTest.kt} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename code/src/test/java/com/expediagroup/sdk/core/okhttp/{OkHttpClientFeatureJavaTest.java => OkHttpClientConfigurationJavaTest.java} (87%) rename code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/{OkHttpClientFeatureTest.kt => OkHttpClientConfigurationTest.kt} (95%) diff --git a/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java b/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java similarity index 87% rename from code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java rename to code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java index 41f8bd40..504b7fef 100644 --- a/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureJavaTest.java +++ b/code/src/test/java/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationJavaTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -public class OkHttpClientFeatureJavaTest { +public class OkHttpClientConfigurationJavaTest { @Test @DisplayName("should access default builder static method") public void defaultStaticBuilderIsAccessibleFromJava() { diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt similarity index 95% rename from code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt rename to code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt index 44c5adb3..bf58d74b 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientFeatureTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfigurationTest.kt @@ -9,10 +9,10 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -class OkHttpClientFeatureTest { +class OkHttpClientConfigurationTest { @Nested - inner class OkHttpClientFeatureBuilderTest { + inner class OkHttpClientConfigurationBuilderTest { @Test fun `builder should build configuration with all properties set`() { // Given From 3c122ee8736da9cd3598d2977fb5a13b7166532b Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 23 Dec 2024 15:42:32 +0300 Subject: [PATCH 03/10] chore: address comments iteration one --- .../expediagroup/sdk/core/common/Feature.kt | 24 +++++++++++++++++++ ...eature.kt => AbstractLogMaskingFeature.kt} | 18 +++++++++++++- .../sdk/core/logging/masking/LogMasker.kt | 16 +++++++++++++ .../logging/masking/MaskingPatternBuilder.kt | 17 +++++++++++++ .../core/logging/masking/PatternBuilders.kt | 16 +++++++++++++ .../common/RequestExecutor.kt | 5 ++++ ...onnectivityLogMasking.kt => LogMasking.kt} | 22 ++++++++++++++--- .../payment/PaymentClient.kt | 5 +--- .../sandbox/SandboxDataManagementClient.kt | 6 +---- .../supply/reservation/ReservationClient.kt | 5 ---- ...st.kt => AbstractLogMaskingFeatureTest.kt} | 12 +++++----- .../sdk/core/logging/masking/LogMaskerTest.kt | 3 ++- .../masking/MaskingPatternBuilderTest.kt | 5 +++- 13 files changed, 128 insertions(+), 26 deletions(-) rename code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/{LogMaskingFeature.kt => AbstractLogMaskingFeature.kt} (57%) rename code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/{LodgingConnectivityLogMasking.kt => LogMasking.kt} (50%) rename code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/{LogMaskingFeatureTest.kt => AbstractLogMaskingFeatureTest.kt} (81%) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt index 2362f8ac..872662fb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt @@ -1,5 +1,29 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.core.common +/** + * Interface representing a feature that can be enabled. + * Implementations of this interface should provide the logic for enabling the feature. + */ internal interface Feature { + /** + * Enables the feature. + * This method should contain the logic required to activate or enable the feature. + */ fun enable() } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt similarity index 57% rename from code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt index 1f05f60d..ef2936bd 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeature.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.core.logging.masking import com.expediagroup.sdk.core.common.Feature @@ -5,7 +21,7 @@ import com.expediagroup.sdk.core.common.Feature /** * Interface representing the configuration for masking. */ -internal abstract class LogMaskingFeature: Feature { +internal abstract class AbstractLogMaskingFeature: Feature { /** * A set of globally masked fields. * diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt index 8986c858..da93523d 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.api.MaskingPattern diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt index ff5b4bf8..893abc4f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.api.MaskingPattern @@ -21,6 +37,7 @@ internal class MaskingPatternBuilder { /** * Adds path-specific fields to be masked. + * This method updates the `globalFields` set by adding the provided field names. * * @param paths Vararg of lists of field names to be masked by path. * @return The current instance of MaskingPatternBuilder. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt index 6afa933d..3914fee6 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.extenstion.builder.json.JsonFullValuePatternBuilder diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt index f6dd2728..74a06fb5 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt @@ -14,6 +14,7 @@ import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.LogMasking internal fun getHttpTransport(configuration: ClientConfiguration): Transport = when (configuration) { is CustomClientConfiguration -> configuration.transport @@ -25,6 +26,10 @@ class RequestExecutor( apiEndpoint: ApiEndpoint ) : AbstractRequestExecutor(getHttpTransport(configuration)) { + init { + LogMasking.enable() + } + override val interceptors: List = listOf( RequestHeadersInterceptor(), LoggingInterceptor(), diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt similarity index 50% rename from code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt index 25a0f422..8829e02c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LodgingConnectivityLogMasking.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt @@ -1,13 +1,29 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.lodgingconnectivity.configuration -import com.expediagroup.sdk.core.logging.masking.LogMaskingFeature +import com.expediagroup.sdk.core.logging.masking.AbstractLogMaskingFeature /** * Object implementing the MaskingConfiguration interface. * Initializes and adds masking patterns based on global and path-specific fields at startup time automatically. */ -internal val LodgingConnectivityLogMasking = - object : LogMaskingFeature() { +internal val LogMasking = + object : AbstractLogMaskingFeature() { /** * A set of globally masked fields. * diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt index 4d841a4a..c49d7e4e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -23,7 +23,6 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.payment.operation.GetPaymentInstrumentResponse import com.expediagroup.sdk.lodgingconnectivity.payment.operation.PaymentInstrumentQuery @@ -39,9 +38,7 @@ import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInst * or timeouts. */ class PaymentClient(config: ClientConfiguration) : GraphQLClient() { - init { - LodgingConnectivityLogMasking.enable() - } + override val apiEndpoint = EndpointProvider.getPaymentApiEndpoint(config.environment) override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt index 61afa4e3..3b61425f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -23,7 +23,6 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput @@ -76,10 +75,7 @@ import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator.Sa * or timeouts. */ class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() { - init { - LodgingConnectivityLogMasking.enable() - } - + override val apiEndpoint = EndpointProvider.getSandboxApiEndpoint(config.environment) override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt index 3d5d7db0..733fe362 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -23,7 +23,6 @@ import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.configuration.LodgingConnectivityLogMasking import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationReconciliationInput @@ -62,10 +61,6 @@ import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream.Reserv */ class ReservationClient(config: ClientConfiguration) : GraphQLClient() { - init { - LodgingConnectivityLogMasking.enable() - } - override val apiEndpoint = EndpointProvider.getSupplyApiEndpoint(config.environment) override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt similarity index 81% rename from code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt rename to code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt index c7259959..51d369c9 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskingFeatureTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt @@ -6,22 +6,22 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_METHOD) -class LogMaskingFeatureTest { +class AbstractLogMaskingFeatureTest { @Test fun `verify globalMaskedFields returns empty set`() { - val maskingConfiguration = object : LogMaskingFeature() {} + val maskingConfiguration = object : AbstractLogMaskingFeature() {} assertTrue(maskingConfiguration.globalMaskedFields.isEmpty()) } @Test fun `verify pathMaskedFields returns empty set`() { - val maskingConfiguration = object : LogMaskingFeature() {} + val maskingConfiguration = object : AbstractLogMaskingFeature() {} assertTrue(maskingConfiguration.pathMaskedFields.isEmpty()) } @Test fun `verify globalMaskedFields is overridable`() { - val maskingConfiguration = object : LogMaskingFeature() { + val maskingConfiguration = object : AbstractLogMaskingFeature() { override val globalMaskedFields: Set get() = setOf("field1", "field2") } @@ -33,7 +33,7 @@ class LogMaskingFeatureTest { @Test fun `verify pathMaskedFields is overridable`() { - val maskingConfiguration = object : LogMaskingFeature() { + val maskingConfiguration = object : AbstractLogMaskingFeature() { override val pathMaskedFields: Set> get() = setOf(listOf("field1", "field2")) } @@ -44,7 +44,7 @@ class LogMaskingFeatureTest { @Test fun `verify pathMaskedFields and globalMaskedFields are overridable interchangeably`() { - val maskingConfiguration = object : LogMaskingFeature() { + val maskingConfiguration = object : AbstractLogMaskingFeature() { override val globalMaskedFields: Set get() = setOf("field1", "field2") override val pathMaskedFields: Set> diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt index 367add44..d20d7b2e 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt @@ -14,7 +14,7 @@ class LogMaskerTest { } @Nested - inner class FieldLogMaskingFeatureTest { + inner class FieldAbstractLogMaskingFeatureTest { @Test fun `adding global fields to the masking pattern builder`() { val globalFields = setOf("globalField1", "globalField2") @@ -44,6 +44,7 @@ class LogMaskerTest { .pathFields(*pathFields.toTypedArray()) .build() + // 1 global field pattern + 2 path field patterns assertEquals(pathFields.size + 1, maskingPatterns.size) } diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt index fdf6fa7e..680fd3cc 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt @@ -1,7 +1,10 @@ package com.expediagroup.sdk.core.logging.masking import com.expediagroup.sdk.core.logging.common.Constant.OMITTED -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test From 7bb5686aefa7e86b772cc8d6f6677eb731aebf23 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 23 Dec 2024 15:50:35 +0300 Subject: [PATCH 04/10] chore: address comments iteration one --- .../sdk/core/logging/masking/MaskingPatternBuilder.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt index 893abc4f..cfdd5576 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt @@ -39,6 +39,9 @@ internal class MaskingPatternBuilder { * Adds path-specific fields to be masked. * This method updates the `globalFields` set by adding the provided field names. * + * Takes the last two elements of each list to be used as the path and ignores the rest. Temporary solution + * until ejmask add support for unlimited-fields path-based masking. + * * @param paths Vararg of lists of field names to be masked by path. * @return The current instance of MaskingPatternBuilder. */ From a80dd53953fe07aaa2b3fa8c80363fdc2546ce91 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 23 Dec 2024 16:17:19 +0300 Subject: [PATCH 05/10] chore: address comments iteration one --- .../logging/masking/PatternBuildersTest.kt | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt index 6afb3206..26df21e2 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt @@ -13,21 +13,6 @@ import org.junit.jupiter.api.TestInstance class PatternBuildersTest { @Nested inner class CustomJsonFullValuePatternBuilderTest { - @Test - fun `is child of JsonFullValuePatternBuilder`() { - val custom = CustomJsonFullValuePatternBuilder() - - assertEquals(JsonFullValuePatternBuilder::class.java, custom::class.java.superclass) - } - - @Test - fun `does not override isGroupable`() { - val original = JsonFullValuePatternBuilder() - val custom = CustomJsonFullValuePatternBuilder() - - assertEquals(original.isGroupable, custom.isGroupable) - } - @Test fun `buildReplacement contains omitted keyword`() { val custom = CustomJsonFullValuePatternBuilder() @@ -55,21 +40,6 @@ class PatternBuildersTest { @Nested inner class CustomJsonRelativeFieldPatternBuilderTest { - @Test - fun `is child of JsonRelativeFieldPatternBuilder`() { - val custom = CustomJsonRelativeFieldPatternBuilder() - - assertEquals(JsonRelativeFieldPatternBuilder::class.java, custom::class.java.superclass) - } - - @Test - fun `does not override isGroupable`() { - val original = JsonRelativeFieldPatternBuilder() - val custom = CustomJsonRelativeFieldPatternBuilder() - - assertEquals(original.isGroupable, custom.isGroupable) - } - @Test fun `buildReplacement contains omitted keyword`() { val custom = CustomJsonRelativeFieldPatternBuilder() From 1fae0b902127b6119006c50ffb361daaedc4bbe3 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 23 Dec 2024 16:18:17 +0300 Subject: [PATCH 06/10] chore: address comments iteration one --- .../sdk/core/logging/masking/PatternBuildersTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt index 26df21e2..f2b2cf37 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuildersTest.kt @@ -1,7 +1,5 @@ package com.expediagroup.sdk.core.logging.masking -import com.ebay.ejmask.extenstion.builder.json.JsonFullValuePatternBuilder -import com.ebay.ejmask.extenstion.builder.json.JsonRelativeFieldPatternBuilder import com.expediagroup.sdk.core.logging.common.Constant.OMITTED import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue From a34cf1012307c9bc81dec165fbf75a9fd64e9cb9 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 23 Dec 2024 16:23:31 +0300 Subject: [PATCH 07/10] chore: address comments iteration one --- .../sdk/core/logging/masking/MaskingPatternBuilderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt index 680fd3cc..1ab8ea10 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt @@ -31,7 +31,6 @@ class MaskingPatternBuilderTest { assertEquals(1, patterns.size) - println(patterns.first().pattern.pattern()) assertFalse(patterns.first().pattern.pattern().contains("first")) assertFalse(patterns.first().pattern.pattern().contains("second")) From 8da7bb87a1e54515e2b2ae4a788d09dd806f7dbb Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Tue, 7 Jan 2025 18:44:30 +0300 Subject: [PATCH 08/10] refactor: eliminate shared state masker and refactor configuration consumption --- .../sdk/core/logging/LoggingInterceptor.kt | 6 +- .../core/logging/common/LoggerDecorator.kt | 5 +- .../masking/AbstractLogMaskingFeature.kt | 49 ------ .../sdk/core/logging/masking/LogMasker.kt | 78 +-------- .../logging/masking/MaskingPatternBuilder.kt | 8 +- .../common/RequestExecutor.kt | 11 +- .../configuration/LogMasking.kt | 43 ----- .../payment/PaymentClient.kt | 19 ++- .../sandbox/SandboxDataManagementClient.kt | 16 +- .../supply/reservation/ReservationClient.kt | 16 +- .../masking/AbstractLogMaskingFeatureTest.kt | 56 ------- .../sdk/core/logging/masking/LogMaskerTest.kt | 156 ++++++++++-------- .../masking/MaskingPatternBuilderTest.kt | 24 +-- 13 files changed, 171 insertions(+), 316 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt delete mode 100644 code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 630ee61d..d7e2de97 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -6,6 +6,7 @@ import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.logging.common.RequestLogger import com.expediagroup.sdk.core.logging.common.ResponseLogger +import org.slf4j.Logger import java.io.IOException import org.slf4j.LoggerFactory @@ -15,6 +16,7 @@ import org.slf4j.LoggerFactory * @param maxBodyLogSize The maximum size of the request/response body to log. Defaults to 1MB. */ class LoggingInterceptor( + private val logger: LoggerDecorator, private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) : Interceptor { @@ -30,8 +32,4 @@ class LoggingInterceptor( return response } - - companion object { - private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java.enclosingClass)) - } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt index e153ae81..58897c98 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt @@ -2,7 +2,10 @@ package com.expediagroup.sdk.core.logging.common import org.slf4j.Logger -class LoggerDecorator(private val logger: Logger) : Logger by logger { +class LoggerDecorator( + private val logger: Logger, + private val masker: (String) -> String = { it } +) : Logger by logger { override fun info(msg: String) = logger.info(decorate(msg)) override fun warn(msg: String) = logger.warn(decorate(msg)) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt deleted file mode 100644 index ef2936bd..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeature.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.core.logging.masking - -import com.expediagroup.sdk.core.common.Feature - -/** - * Interface representing the configuration for masking. - */ -internal abstract class AbstractLogMaskingFeature: Feature { - /** - * A set of globally masked fields. - * - * @return A set of strings representing the global masked fields. - */ - open val globalMaskedFields: Set - get() = emptySet() - - /** - * A set of path-specific masked fields. - * - * @return A set of lists of strings representing the path masked fields. - */ - open val pathMaskedFields: Set> - get() = emptySet() - - override fun enable() { - mask.addPatternIfNotExists( - *MaskingPatternBuilder().apply { - globalFields(*globalMaskedFields.toTypedArray()) - pathFields(*pathMaskedFields.toTypedArray()) - }.build().toTypedArray() - ) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt index da93523d..3681d066 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt @@ -18,36 +18,14 @@ package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.api.MaskingPattern -/** - * Interface for pattern-based masking. - */ -internal interface PatternBasedMask : (String) -> String { - /** - * Adds masking patterns if they do not already exist. - * - * @param maskingPatterns Vararg of MaskingPattern to be added. - * @return True if patterns were added, false if they already existed. - */ - fun addPatternIfNotExists(vararg maskingPatterns: MaskingPattern): Boolean - - /** - * Retrieves the list of current masking patterns. - * - * @return A list of MaskingPattern. - */ - fun patterns(): List - - /** - * Clears all existing masking patterns. - */ - fun clear() -} - -/** - * Object implementing the PatternBasedMask interface. - */ -internal val mask = object : PatternBasedMask { - val patterns: MutableList = mutableListOf() +internal class LogMasker( + globalMaskedFields: Set = emptySet(), + pathMaskedFields: Set> = emptySet() +) : (String) -> String { + private val patterns: List = MaskingPatternBuilder().apply { + globalFields(globalMaskedFields) + pathFields(pathMaskedFields) + }.build() /** * Applies all masking patterns to the input string. @@ -64,44 +42,4 @@ internal val mask = object : PatternBasedMask { return masked } - - /** - * Retrieves the list of current masking patterns. - * - * @return A list of MaskingPattern. - */ - override fun patterns() = - this.patterns.toList() - - /** - * Clears all existing masking patterns. - */ - override fun clear() { - patterns.clear() - } - - /** - * Adds masking patterns if they do not already exist. - * - * @param maskingPatterns Vararg of MaskingPattern to be added. - * @return True if patterns were added, false if they already existed. - */ - override fun addPatternIfNotExists(vararg maskingPatterns: MaskingPattern): Boolean { - var addedPattern = false - - maskingPatterns.forEach newPatterns@{ pattern -> - patterns.forEach existingPatterns@{ - val patternExists = (it.pattern.pattern() == pattern.pattern.pattern()) - .and(it.replacement == pattern.replacement) - - if (patternExists) { - return@newPatterns - } - } - - patterns.add(pattern).also { addedPattern = true } - } - - return addedPattern - } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt index cfdd5576..5c25ffed 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt @@ -28,11 +28,11 @@ internal class MaskingPatternBuilder { /** * Adds global fields to be masked. * - * @param name Vararg of field names to be masked globally. + * @param fields Vararg of field names to be masked globally. * @return The current instance of MaskingPatternBuilder. */ - fun globalFields(vararg name: String): MaskingPatternBuilder = apply { - globalFields += name.toSortedSet() + fun globalFields(fields: Set): MaskingPatternBuilder = apply { + globalFields += fields.toSortedSet() } /** @@ -45,7 +45,7 @@ internal class MaskingPatternBuilder { * @param paths Vararg of lists of field names to be masked by path. * @return The current instance of MaskingPatternBuilder. */ - fun pathFields(vararg paths: List) = apply { + fun pathFields(paths: Set>) = apply { pathFields += paths.map { it.takeLast(2) } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt index 74a06fb5..b65ff07a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt @@ -8,13 +8,13 @@ import com.expediagroup.sdk.core.client.Transport import com.expediagroup.sdk.core.common.RequestHeadersInterceptor import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.logging.LoggingInterceptor +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient import com.expediagroup.sdk.core.okhttp.OkHttpTransport import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.configuration.LogMasking internal fun getHttpTransport(configuration: ClientConfiguration): Transport = when (configuration) { is CustomClientConfiguration -> configuration.transport @@ -23,16 +23,13 @@ internal fun getHttpTransport(configuration: ClientConfiguration): Transport = w class RequestExecutor( configuration: ClientConfiguration, - apiEndpoint: ApiEndpoint + apiEndpoint: ApiEndpoint, + logger: LoggerDecorator ) : AbstractRequestExecutor(getHttpTransport(configuration)) { - init { - LogMasking.enable() - } - override val interceptors: List = listOf( RequestHeadersInterceptor(), - LoggingInterceptor(), + LoggingInterceptor(logger), BearerAuthenticationInterceptor( BearerAuthenticationManager( requestExecutor = this, diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt deleted file mode 100644 index 8829e02c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/LogMasking.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.configuration - -import com.expediagroup.sdk.core.logging.masking.AbstractLogMaskingFeature - -/** - * Object implementing the MaskingConfiguration interface. - * Initializes and adds masking patterns based on global and path-specific fields at startup time automatically. - */ -internal val LogMasking = - object : AbstractLogMaskingFeature() { - /** - * A set of globally masked fields. - * - * @return A set of strings representing the global masked fields. - */ - override val globalMaskedFields: Set = setOf( - "cvv", - "cvv2", - ) - - /** - * A set of path-specific masked fields. - * - * @return A set of lists of strings representing the path masked fields. - */ - override val pathMaskedFields: Set> = setOf() - } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt index b1bc2412..5d24a8d3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -16,6 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.payment +import com.expediagroup.sdk.core.logging.common.LoggerDecorator +import com.expediagroup.sdk.core.logging.masking.LogMasker import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.GraphQLClient @@ -27,6 +29,7 @@ import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.payment.operation.GetPaymentInstrumentResponse import com.expediagroup.sdk.lodgingconnectivity.payment.operation.PaymentInstrumentQuery import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInstrumentOperation +import org.slf4j.LoggerFactory /** * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. @@ -42,7 +45,11 @@ class PaymentClient(config: ClientConfiguration) : GraphQLClient() { override val apiEndpoint = EndpointProvider.getPaymentApiEndpoint(config.environment) override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor(config, apiEndpoint), + requestExecutor = RequestExecutor( + config, + apiEndpoint, + logger + ), serverUrl = apiEndpoint.endpoint ) @@ -60,4 +67,14 @@ class PaymentClient(config: ClientConfiguration) : GraphQLClient() { fun getPaymentInstrument(token: String): GetPaymentInstrumentResponse = run { getPaymentInstrumentOperation(graphQLExecutor, token) } + + companion object { + @JvmStatic + private val logger = LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ) + ) + } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt index f10620c7..782bd587 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -16,6 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.sandbox +import com.expediagroup.sdk.core.logging.common.LoggerDecorator +import com.expediagroup.sdk.core.logging.masking.LogMasker import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.GraphQLClient @@ -24,6 +26,7 @@ import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.payment.PaymentClient import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreatePropertyInput @@ -61,6 +64,7 @@ import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.ge import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.getSandboxReservationsOperation import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.updateSandboxReservationOperation import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator.SandboxReservationsPaginator +import org.slf4j.LoggerFactory /** * A client for interacting with EG Lodging Connectivity Sandbox GraphQL API. @@ -79,7 +83,7 @@ class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() override val apiEndpoint = EndpointProvider.getSandboxApiEndpoint(config.environment) override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor(config, apiEndpoint), + requestExecutor = RequestExecutor(config, apiEndpoint, logger), serverUrl = apiEndpoint.endpoint ) @@ -312,4 +316,14 @@ class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() fun deleteReservations(input: DeletePropertyReservationsInput): DeleteSandboxReservationsResponse = run { deleteSandboxReservationsOperation(graphQLExecutor, input) } + + companion object { + @JvmStatic + private val logger = LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ) + ) + } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt index 24aa880e..cb013aad 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -16,6 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.supply.reservation +import com.expediagroup.sdk.core.logging.common.LoggerDecorator +import com.expediagroup.sdk.core.logging.masking.LogMasker import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.GraphQLClient @@ -24,6 +26,7 @@ import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.payment.PaymentClient import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationInput import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationReconciliationInput import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelVrboReservationInput @@ -46,6 +49,7 @@ import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.con import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.refundReservationOperation import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.paginator.ReservationsPaginator import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream.ReservationsStream +import org.slf4j.LoggerFactory /** * A client for interacting with EG Lodging Connectivity Reservations GraphQL API @@ -64,7 +68,7 @@ class ReservationClient(config: ClientConfiguration) : GraphQLClient() { override val apiEndpoint = EndpointProvider.getSupplyApiEndpoint(config.environment) override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor(config, apiEndpoint), + requestExecutor = RequestExecutor(config, apiEndpoint, logger), serverUrl = apiEndpoint.endpoint ) @@ -277,4 +281,14 @@ class ReservationClient(config: ClientConfiguration) : GraphQLClient() { ): RefundReservationResponse = run { refundReservationOperation(graphQLExecutor, input, selections) } + + companion object { + @JvmStatic + private val logger = LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ) + ) + } } diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt deleted file mode 100644 index 51d369c9..00000000 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/AbstractLogMaskingFeatureTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance - -@TestInstance(TestInstance.Lifecycle.PER_METHOD) -class AbstractLogMaskingFeatureTest { - @Test - fun `verify globalMaskedFields returns empty set`() { - val maskingConfiguration = object : AbstractLogMaskingFeature() {} - assertTrue(maskingConfiguration.globalMaskedFields.isEmpty()) - } - - @Test - fun `verify pathMaskedFields returns empty set`() { - val maskingConfiguration = object : AbstractLogMaskingFeature() {} - assertTrue(maskingConfiguration.pathMaskedFields.isEmpty()) - } - - @Test - fun `verify globalMaskedFields is overridable`() { - val maskingConfiguration = object : AbstractLogMaskingFeature() { - override val globalMaskedFields: Set - get() = setOf("field1", "field2") - } - - assertEquals(2, maskingConfiguration.globalMaskedFields.size) - assertTrue(maskingConfiguration.globalMaskedFields.contains("field1")) - assertTrue(maskingConfiguration.globalMaskedFields.contains("field2")) - } - - @Test - fun `verify pathMaskedFields is overridable`() { - val maskingConfiguration = object : AbstractLogMaskingFeature() { - override val pathMaskedFields: Set> - get() = setOf(listOf("field1", "field2")) - } - - assertEquals(1, maskingConfiguration.pathMaskedFields.size) - assertTrue(maskingConfiguration.pathMaskedFields.contains(listOf("field1", "field2"))) - } - - @Test - fun `verify pathMaskedFields and globalMaskedFields are overridable interchangeably`() { - val maskingConfiguration = object : AbstractLogMaskingFeature() { - override val globalMaskedFields: Set - get() = setOf("field1", "field2") - override val pathMaskedFields: Set> - get() = setOf(listOf("field3", "field4")) - } - - assertEquals(maskingConfiguration.globalMaskedFields.size, 2) - } -} diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt index d20d7b2e..cffa2222 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt @@ -1,24 +1,17 @@ package com.expediagroup.sdk.core.logging.masking -import com.ebay.ejmask.api.MaskingPattern import com.expediagroup.sdk.core.logging.common.Constant.OMITTED -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class LogMaskerTest { - @AfterEach - fun afterEach() { - mask.clear() - } - @Nested - inner class FieldAbstractLogMaskingFeatureTest { + inner class FieldMaskingConfigurationTest { @Test fun `adding global fields to the masking pattern builder`() { val globalFields = setOf("globalField1", "globalField2") - val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().globalFields(globalFields).build() assertEquals(1, patterns.size) @@ -29,7 +22,7 @@ class LogMaskerTest { @Test fun `adding path fields to the masking pattern builder`() { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(pathFields.size, patterns.size) } @@ -40,105 +33,134 @@ class LogMaskerTest { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) val maskingPatterns = MaskingPatternBuilder() - .globalFields(*globalFields.toTypedArray()) - .pathFields(*pathFields.toTypedArray()) + .globalFields(globalFields) + .pathFields(pathFields) .build() // 1 global field pattern + 2 path field patterns assertEquals(pathFields.size + 1, maskingPatterns.size) } + } + @Nested + inner class MaskingBehaviorTest { @Test - fun `masking patterns are not added to the mask if they already exist`() { - val maskingPatterns = listOf( - MaskingPattern(0, "pattern1", "replacement1"), - MaskingPattern(1, "pattern2", "replacement2") - ) - mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) - - assertEquals(maskingPatterns, mask.patterns()) - - val newMaskingPatterns = listOf( - MaskingPattern(0, "pattern1", "replacement1"), - MaskingPattern(1, "pattern2", "replacement2") - ) - - val addResult = mask.addPatternIfNotExists(*newMaskingPatterns.toTypedArray()) + fun `masks global fields in json payload`() { + val globalFields = setOf("globalField1", "globalField2") + val patterns = MaskingPatternBuilder().globalFields(globalFields).build() - assertFalse(addResult) - assertEquals(maskingPatterns, mask.patterns()) - } + assertEquals(1, patterns.size) - @Test - fun `masking patterns are cleared from the mask`() { - val maskingPatterns = listOf( - MaskingPattern(0, "pattern1", "replacement1"), - MaskingPattern(1, "pattern2", "replacement2") + val actual = patterns.first().replaceAll( + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + } + } + """.trimIndent() ) - mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) - - assertTrue(mask.patterns().isNotEmpty()) - assertEquals(maskingPatterns.size, mask.patterns().size) - assertEquals(maskingPatterns, mask.patterns()) - - mask.clear() + val expected = """ + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + } + } + """.trimIndent() - assertTrue(mask.patterns().isEmpty()) + assertEquals(expected, actual) } - } - @Nested - inner class MaskingBehaviorTest { @Test - fun `masking patterns are applied to the input string`() { - val maskingPatterns = listOf( - MaskingPattern(0, "pattern1", "replacement1"), - MaskingPattern(1, "pattern2", "replacement2") - ) + fun `masks path fields in json payload`() { + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() - mask.addPatternIfNotExists(*maskingPatterns.toTypedArray()) + assertEquals(pathFields.size, patterns.size) - val masked = mask( - """ + var actual = """ { - "pattern1": "value1", - "pattern2": "value2" + "first": { + "second": "value1" + }, + "first1": { + "second1": { + "third1": { + "as": "value2" + } + } + } } """.trimIndent() - ) + patterns.forEach { actual = it.replaceAll(actual) } val expected = """ { - "replacement1": "value1", - "replacement2": "value2" + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } + } + } } """.trimIndent() - assertEquals(expected, masked) + assertEquals(expected, actual) } @Test - fun `masks global fields in json payload`() { + fun `masks global and path fields in json payload`() { val globalFields = setOf("globalField1", "globalField2") - val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - assertEquals(1, patterns.size) + val maskingPatterns = MaskingPatternBuilder() + .globalFields(globalFields) + .pathFields(pathFields) + .build() - val actual = patterns.first().replaceAll( - """ + assertEquals(pathFields.size + 1, maskingPatterns.size) + + var actual = """ { "globalField1": "value1", "globalField2": { "globalField1": "value2" + }, + "first": { + "second": "value1" + }, + "first1": { + "second1": { + "third1": { + "as": "value2" + } + } } } """.trimIndent() - ) - val expected = """ + maskingPatterns.forEach { actual = it.replaceAll(actual) } + + val expected = """ { "globalField1": "$OMITTED", "globalField2": { "globalField1": "$OMITTED" + }, + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } + } } } """.trimIndent() diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt index 1ab8ea10..cb0919f8 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt @@ -19,7 +19,7 @@ class MaskingPatternBuilderTest { @Test fun `generated masking patterns size is one when only global fields are passed regardless of their count`() { val globalFields = setOf("globalField1", "globalField2") - val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().globalFields(globalFields).build() assertEquals(1, patterns.size) } @@ -27,7 +27,7 @@ class MaskingPatternBuilderTest { @Test fun `passed path fields are truncated to the last two elements`() { val pathFields = setOf(listOf("first", "second", "third", "fourth")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(1, patterns.size) @@ -41,7 +41,7 @@ class MaskingPatternBuilderTest { @Test fun `generated masking patterns size is not affected by pattern duplication caused by truncated patterns`() { val pathFields = setOf(listOf("second", "third"), listOf("first", "second", "third")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(1, patterns.size) } @@ -49,7 +49,7 @@ class MaskingPatternBuilderTest { @Test fun `generated masking patterns size grows linearly when only path-based fields are passed`() { val pathFields = setOf(listOf("first", "second"), listOf("first", "second", "third")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(pathFields.size, patterns.size) } @@ -60,8 +60,8 @@ class MaskingPatternBuilderTest { val pathFields = setOf(listOf("first", "second"), listOf("first", "second", "third")) val maskingPatterns = MaskingPatternBuilder() - .globalFields(*globalFields.toTypedArray()) - .pathFields(*pathFields.toTypedArray()) + .globalFields(globalFields) + .pathFields(pathFields) .build() assertEquals(pathFields.size + 1, maskingPatterns.size) @@ -70,7 +70,7 @@ class MaskingPatternBuilderTest { @Test fun `global path fields are passed to builder and consumed in masking patterns generation`() { val globalFields = setOf("globalField1", "globalField2") - val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().globalFields(globalFields).build() assertEquals(1, patterns.size) @@ -81,7 +81,7 @@ class MaskingPatternBuilderTest { @Test fun `path fields are passed to builder and consumed in masking patterns generation`() { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(pathFields.size, patterns.size) @@ -101,7 +101,7 @@ class MaskingPatternBuilderTest { @Test fun `masks a field globally when a global field is passed`() { val globalFields = setOf("globalField1", "globalField2") - val patterns = MaskingPatternBuilder().globalFields(*globalFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().globalFields(globalFields).build() assertEquals(1, patterns.size) @@ -130,7 +130,7 @@ class MaskingPatternBuilderTest { @Test fun `masks a field in a path when a path field is passed`() { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val patterns = MaskingPatternBuilder().pathFields(*pathFields.toTypedArray()).build() + val patterns = MaskingPatternBuilder().pathFields(pathFields).build() assertEquals(pathFields.size, patterns.size) @@ -175,8 +175,8 @@ class MaskingPatternBuilderTest { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) val maskingPatterns = MaskingPatternBuilder() - .globalFields(*globalFields.toTypedArray()) - .pathFields(*pathFields.toTypedArray()) + .globalFields(globalFields) + .pathFields(pathFields) .build() assertEquals(pathFields.size + 1, maskingPatterns.size) From 0e6082d9ba62c872adcc288c27258618d4738d06 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sun, 12 Jan 2025 14:19:37 +0300 Subject: [PATCH 09/10] refactor: cleanup --- .../expediagroup/sdk/core/common/Feature.kt | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt deleted file mode 100644 index 872662fb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/common/Feature.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.core.common - -/** - * Interface representing a feature that can be enabled. - * Implementations of this interface should provide the logic for enabling the feature. - */ -internal interface Feature { - /** - * Enables the feature. - * This method should contain the logic required to activate or enable the feature. - */ - fun enable() -} From 58bb8bc0bfee920a17f94dde3b363ed1a0a9b47d Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 13 Jan 2025 16:51:58 +0300 Subject: [PATCH 10/10] chore: resolve conflicts and satisfy new lint rules --- .../sdk/core/logging/LoggingInterceptor.kt | 2 +- .../core/logging/common/LoggerDecorator.kt | 2 +- .../core/logging/masking/JsonFieldFilter.kt | 19 -- .../masking/JsonFieldPatternBuilder.kt | 17 -- .../sdk/core/logging/masking/LogMasker.kt | 12 +- .../sdk/core/logging/masking/MaskLogsUtils.kt | 0 .../logging/masking/MaskingPatternBuilder.kt | 92 ++++---- .../core/logging/masking/PatternBuilders.kt | 14 +- .../common/RequestExecutor.kt | 23 +- .../payment/PaymentClient.kt | 33 +-- .../sandbox/SandboxDataManagementClient.kt | 21 +- .../supply/reservation/ReservationClient.kt | 24 +- .../sdk/core/logging/masking/LogMaskerTest.kt | 71 ++++-- .../masking/MaskingPatternBuilderTest.kt | 214 +++++++++++------- 14 files changed, 295 insertions(+), 249 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 46149802..03bef079 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -16,7 +16,7 @@ import java.io.IOException */ class LoggingInterceptor( private val logger: LoggerDecorator, - private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE + private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE, ) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt index 16a8bf47..f969ff65 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt @@ -4,7 +4,7 @@ import org.slf4j.Logger class LoggerDecorator( private val logger: Logger, - private val masker: (String) -> String = { it } + private val masker: (String) -> String = { it }, ) : Logger by logger { override fun info(msg: String) = logger.info(decorate(msg)) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt deleted file mode 100644 index b36f1b2a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import com.ebay.ejmask.core.BaseFilter - -/** - * A filter class that extends the BaseFilter to apply masking on specific JSON fields using - * `ExpediaGroupJsonFieldPatternBuilder` for pattern building. - * - * This filter helps in masking sensitive JSON fields by replacing them with a predefined pattern. - * - * @constructor - * Initializes ExpediaGroupJsonFieldFilter with the specified fields to be masked. - * - * @param maskedFields An array of strings representing the names of the fields to be masked. - */ -internal class JsonFieldFilter(maskedFields: Array) : BaseFilter( - JsonFieldPatternBuilder::class.java, - *maskedFields -) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt deleted file mode 100644 index 82543b08..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.expediagroup.sdk.core.logging.masking - -import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder -import com.expediagroup.sdk.core.logging.common.Constant.OMITTED - -/** - * A builder class for creating JSON field replacement patterns specifically for Expedia Group. - * - * This class extends the `JsonFieldPatternBuilder` and provides an implementation for building - * replacement patterns for JSON field masking. - * - * The replacement pattern format generated by this builder is structured to conceal sensitive - * data while keeping a specified number of characters visible. - */ -internal class JsonFieldPatternBuilder : JsonFieldPatternBuilder() { - override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = "\"$1$2$OMITTED\"" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt index 3681d066..e69cced3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/LogMasker.kt @@ -20,12 +20,14 @@ import com.ebay.ejmask.api.MaskingPattern internal class LogMasker( globalMaskedFields: Set = emptySet(), - pathMaskedFields: Set> = emptySet() + pathMaskedFields: Set> = emptySet(), ) : (String) -> String { - private val patterns: List = MaskingPatternBuilder().apply { - globalFields(globalMaskedFields) - pathFields(pathMaskedFields) - }.build() + private val patterns: List = + MaskingPatternBuilder() + .apply { + globalFields(globalMaskedFields) + pathFields(pathMaskedFields) + }.build() /** * Applies all masking patterns to the input string. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt index 5c25ffed..6a0c7dd2 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilder.kt @@ -31,9 +31,10 @@ internal class MaskingPatternBuilder { * @param fields Vararg of field names to be masked globally. * @return The current instance of MaskingPatternBuilder. */ - fun globalFields(fields: Set): MaskingPatternBuilder = apply { - globalFields += fields.toSortedSet() - } + fun globalFields(fields: Set): MaskingPatternBuilder = + apply { + globalFields += fields.toSortedSet() + } /** * Adds path-specific fields to be masked. @@ -45,55 +46,58 @@ internal class MaskingPatternBuilder { * @param paths Vararg of lists of field names to be masked by path. * @return The current instance of MaskingPatternBuilder. */ - fun pathFields(paths: Set>) = apply { - pathFields += paths.map { it.takeLast(2) } - } + fun pathFields(paths: Set>) = + apply { + pathFields += paths.map { it.takeLast(2) } + } /** * Builds the list of MaskingPattern based on the added global and path fields. * * @return A list of MaskingPattern. */ - fun build(): List = buildList { - /** - * Builds masking patterns for global fields. - * - * @return A list of MaskingPattern for global fields. - */ - fun buildGlobalFieldsMaskingPattern(): List = - if (globalFields.isEmpty()) { - emptyList() - } else { - val patternGenerator = CustomJsonFullValuePatternBuilder() - listOf( - MaskingPattern( - 0, - patternGenerator.buildPattern(0, *globalFields.toTypedArray()), - patternGenerator.buildReplacement(0, *globalFields.toTypedArray()) - ) - ) - } - - /** - * Builds masking patterns for path-specific fields. - * - * @return A list of MaskingPattern for path-specific fields. - */ - fun buildPathFieldsMaskingPattern(): List = buildList { - pathFields.forEachIndexed { index, path -> - CustomJsonRelativeFieldPatternBuilder().also { patternGenerator -> - add( + fun build(): List = + buildList { + /** + * Builds masking patterns for global fields. + * + * @return A list of MaskingPattern for global fields. + */ + fun buildGlobalFieldsMaskingPattern(): List = + if (globalFields.isEmpty()) { + emptyList() + } else { + val patternGenerator = CustomJsonFullValuePatternBuilder() + listOf( MaskingPattern( - index + 1, - patternGenerator.buildPattern(1, *path.toTypedArray()), - patternGenerator.buildReplacement(1, *path.toTypedArray()) - ) + 0, + patternGenerator.buildPattern(0, *globalFields.toTypedArray()), + patternGenerator.buildReplacement(0, *globalFields.toTypedArray()), + ), ) } - } - } - addAll(buildGlobalFieldsMaskingPattern()) - addAll(buildPathFieldsMaskingPattern()) - } + /** + * Builds masking patterns for path-specific fields. + * + * @return A list of MaskingPattern for path-specific fields. + */ + fun buildPathFieldsMaskingPattern(): List = + buildList { + pathFields.forEachIndexed { index, path -> + CustomJsonRelativeFieldPatternBuilder().also { patternGenerator -> + add( + MaskingPattern( + index + 1, + patternGenerator.buildPattern(1, *path.toTypedArray()), + patternGenerator.buildReplacement(1, *path.toTypedArray()), + ), + ) + } + } + } + + addAll(buildGlobalFieldsMaskingPattern()) + addAll(buildPathFieldsMaskingPattern()) + } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt index 3914fee6..de4da35a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/PatternBuilders.kt @@ -20,12 +20,10 @@ import com.ebay.ejmask.extenstion.builder.json.JsonFullValuePatternBuilder import com.ebay.ejmask.extenstion.builder.json.JsonRelativeFieldPatternBuilder import com.expediagroup.sdk.core.logging.common.Constant.OMITTED - /** * Custom implementation of JsonFieldPatternBuilder for building JSON field patterns. */ internal class CustomJsonFullValuePatternBuilder : JsonFullValuePatternBuilder() { - companion object { @JvmStatic val REPLACEMENT_TEMPLATE = "\"$1$2$OMITTED\"" @@ -39,8 +37,10 @@ internal class CustomJsonFullValuePatternBuilder : JsonFullValuePatternBuilder() * @param fieldNames Vararg of field names. * @return The replacement string. */ - override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = - REPLACEMENT_TEMPLATE + override fun buildReplacement( + visibleCharacters: Int, + vararg fieldNames: String?, + ): String = REPLACEMENT_TEMPLATE } internal class CustomJsonRelativeFieldPatternBuilder : JsonRelativeFieldPatternBuilder() { @@ -57,6 +57,8 @@ internal class CustomJsonRelativeFieldPatternBuilder : JsonRelativeFieldPatternB * @param fieldNames Vararg of field names. * @return The replacement string. */ - override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = - REPLACEMENT_TEMPLATE + override fun buildReplacement( + visibleCharacters: Int, + vararg fieldNames: String?, + ): String = REPLACEMENT_TEMPLATE } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt index e5c0e922..ceaf07c8 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutor.kt @@ -8,6 +8,7 @@ import com.expediagroup.sdk.core.client.Transport import com.expediagroup.sdk.core.common.RequestHeadersInterceptor import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.logging.LoggingInterceptor +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient import com.expediagroup.sdk.core.okhttp.OkHttpTransport import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint @@ -26,16 +27,16 @@ class RequestExecutor( apiEndpoint: ApiEndpoint, logger: LoggerDecorator, ) : AbstractRequestExecutor(getHttpTransport(configuration)) { - - override val interceptors: List = listOf( - RequestHeadersInterceptor(), - LoggingInterceptor(logger), - BearerAuthenticationInterceptor( - BearerAuthenticationManager( - requestExecutor = this, - authUrl = apiEndpoint.authEndpoint, - credentials = Credentials(configuration.key, configuration.secret), - ) + override val interceptors: List = + listOf( + RequestHeadersInterceptor(), + LoggingInterceptor(logger), + BearerAuthenticationInterceptor( + BearerAuthenticationManager( + requestExecutor = this, + authUrl = apiEndpoint.authEndpoint, + credentials = Credentials(configuration.key, configuration.secret), + ), + ), ) - ) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt index 68b99f81..42375d2f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -19,9 +19,9 @@ package com.expediagroup.sdk.lodgingconnectivity.payment import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.logging.masking.LogMasker import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.common.AbstractGraphQLExecutor import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.GraphQLClient -import com.expediagroup.sdk.graphql.common.AbstractGraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment @@ -29,6 +29,7 @@ import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.payment.operation.GetPaymentInstrumentResponse import com.expediagroup.sdk.lodgingconnectivity.payment.operation.PaymentInstrumentQuery import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInstrumentOperation +import org.slf4j.LoggerFactory /** * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. @@ -44,14 +45,16 @@ class PaymentClient( ) : GraphQLClient() { override val apiEndpoint = EndpointProvider.getPaymentApiEndpoint(config.environment) - override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor( - config, - apiEndpoint, - logger - ), - serverUrl = apiEndpoint.endpoint - ) + override val graphQLExecutor: AbstractGraphQLExecutor = + GraphQLExecutor( + requestExecutor = + RequestExecutor( + config, + apiEndpoint, + logger, + ), + serverUrl = apiEndpoint.endpoint, + ) /** * Retrieves the payment instrument details associated with the specified token. @@ -71,11 +74,13 @@ class PaymentClient( companion object { @JvmStatic - private val logger = LoggerDecorator( - logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), - masker = LogMasker( - globalMaskedFields = setOf("cvv", "cvv2"), + private val logger = + LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = + LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ), ) - ) } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt index 58b80182..4bac760e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -83,10 +83,11 @@ class SandboxDataManagementClient( ) : GraphQLClient() { override val apiEndpoint = EndpointProvider.getSandboxApiEndpoint(config.environment) - override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor(config, apiEndpoint, logger), - serverUrl = apiEndpoint.endpoint - ) + override val graphQLExecutor: AbstractGraphQLExecutor = + GraphQLExecutor( + requestExecutor = RequestExecutor(config, apiEndpoint, logger), + serverUrl = apiEndpoint.endpoint, + ) /** * Retrieves all sandbox properties in one page. @@ -341,11 +342,13 @@ class SandboxDataManagementClient( companion object { @JvmStatic - private val logger = LoggerDecorator( - logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), - masker = LogMasker( - globalMaskedFields = setOf("cvv", "cvv2"), + private val logger = + LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = + LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ), ) - ) } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt index 0c11f23e..dd915791 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -22,7 +22,6 @@ import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceExce import com.expediagroup.sdk.graphql.common.AbstractGraphQLExecutor import com.expediagroup.sdk.graphql.common.GraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.GraphQLClient -import com.expediagroup.sdk.graphql.common.AbstractGraphQLExecutor import com.expediagroup.sdk.lodgingconnectivity.common.RequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment @@ -69,10 +68,11 @@ class ReservationClient( ) : GraphQLClient() { override val apiEndpoint = EndpointProvider.getSupplyApiEndpoint(config.environment) - override val graphQLExecutor: AbstractGraphQLExecutor = GraphQLExecutor( - requestExecutor = RequestExecutor(config, apiEndpoint, logger), - serverUrl = apiEndpoint.endpoint - ) + override val graphQLExecutor: AbstractGraphQLExecutor = + GraphQLExecutor( + requestExecutor = RequestExecutor(config, apiEndpoint, logger), + serverUrl = apiEndpoint.endpoint, + ) /** * Creates a paginator for retrieving paginated reservation data for a specified property. @@ -290,7 +290,7 @@ class ReservationClient( @JvmOverloads fun refundReservation( input: RefundReservationInput, - selections: ReservationSelections? = null + selections: ReservationSelections? = null, ): RefundReservationResponse = run { refundReservationOperation(graphQLExecutor, input, selections) @@ -298,11 +298,13 @@ class ReservationClient( companion object { @JvmStatic - private val logger = LoggerDecorator( - logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), - masker = LogMasker( - globalMaskedFields = setOf("cvv", "cvv2"), + private val logger = + LoggerDecorator( + logger = LoggerFactory.getLogger(PaymentClient::class.java.enclosingClass), + masker = + LogMasker( + globalMaskedFields = setOf("cvv", "cvv2"), + ), ) - ) } } diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt index cffa2222..c426c42a 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/LogMaskerTest.kt @@ -1,7 +1,8 @@ package com.expediagroup.sdk.core.logging.masking import com.expediagroup.sdk.core.logging.common.Constant.OMITTED -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -15,8 +16,20 @@ class LogMaskerTest { assertEquals(1, patterns.size) - assertTrue(patterns.first().pattern.pattern().contains("globalField1")) - assertTrue(patterns.first().pattern.pattern().contains("globalField2")) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("globalField1"), + ) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("globalField2"), + ) } @Test @@ -32,10 +45,11 @@ class LogMaskerTest { val globalFields = setOf("globalField1", "globalField2") val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val maskingPatterns = MaskingPatternBuilder() - .globalFields(globalFields) - .pathFields(pathFields) - .build() + val maskingPatterns = + MaskingPatternBuilder() + .globalFields(globalFields) + .pathFields(pathFields) + .build() // 1 global field pattern + 2 path field patterns assertEquals(pathFields.size + 1, maskingPatterns.size) @@ -51,17 +65,19 @@ class LogMaskerTest { assertEquals(1, patterns.size) - val actual = patterns.first().replaceAll( - """ - { - "globalField1": "value1", - "globalField2": { - "globalField1": "value2" + val actual = + patterns.first().replaceAll( + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + } } - } - """.trimIndent() - ) - val expected = """ + """.trimIndent(), + ) + val expected = + """ { "globalField1": "$OMITTED", "globalField2": { @@ -80,7 +96,8 @@ class LogMaskerTest { assertEquals(pathFields.size, patterns.size) - var actual = """ + var actual = + """ { "first": { "second": "value1" @@ -96,7 +113,8 @@ class LogMaskerTest { """.trimIndent() patterns.forEach { actual = it.replaceAll(actual) } - val expected = """ + val expected = + """ { "first": { "second": "$OMITTED" @@ -119,14 +137,16 @@ class LogMaskerTest { val globalFields = setOf("globalField1", "globalField2") val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val maskingPatterns = MaskingPatternBuilder() - .globalFields(globalFields) - .pathFields(pathFields) - .build() + val maskingPatterns = + MaskingPatternBuilder() + .globalFields(globalFields) + .pathFields(pathFields) + .build() assertEquals(pathFields.size + 1, maskingPatterns.size) - var actual = """ + var actual = + """ { "globalField1": "value1", "globalField2": { @@ -146,7 +166,8 @@ class LogMaskerTest { """.trimIndent() maskingPatterns.forEach { actual = it.replaceAll(actual) } - val expected = """ + val expected = + """ { "globalField1": "$OMITTED", "globalField2": { diff --git a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt index cb0919f8..0a622335 100644 --- a/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt +++ b/code/src/test/kotlin/com/expediagroup/sdk/core/logging/masking/MaskingPatternBuilderTest.kt @@ -1,12 +1,12 @@ package com.expediagroup.sdk.core.logging.masking import com.expediagroup.sdk.core.logging.common.Constant.OMITTED -import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll class MaskingPatternBuilderTest { @Nested @@ -31,11 +31,35 @@ class MaskingPatternBuilderTest { assertEquals(1, patterns.size) - assertFalse(patterns.first().pattern.pattern().contains("first")) - assertFalse(patterns.first().pattern.pattern().contains("second")) + assertFalse( + patterns + .first() + .pattern + .pattern() + .contains("first"), + ) + assertFalse( + patterns + .first() + .pattern + .pattern() + .contains("second"), + ) - assertTrue(patterns.first().pattern.pattern().contains("third")) - assertTrue(patterns.first().pattern.pattern().contains("fourth")) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("third"), + ) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("fourth"), + ) } @Test @@ -59,10 +83,11 @@ class MaskingPatternBuilderTest { val globalFields = setOf("globalField1", "globalField2") val pathFields = setOf(listOf("first", "second"), listOf("first", "second", "third")) - val maskingPatterns = MaskingPatternBuilder() - .globalFields(globalFields) - .pathFields(pathFields) - .build() + val maskingPatterns = + MaskingPatternBuilder() + .globalFields(globalFields) + .pathFields(pathFields) + .build() assertEquals(pathFields.size + 1, maskingPatterns.size) } @@ -74,8 +99,20 @@ class MaskingPatternBuilderTest { assertEquals(1, patterns.size) - assertTrue(patterns.first().pattern.pattern().contains("globalField1")) - assertTrue(patterns.first().pattern.pattern().contains("globalField2")) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("globalField1"), + ) + assertTrue( + patterns + .first() + .pattern + .pattern() + .contains("globalField2"), + ) } @Test @@ -83,15 +120,13 @@ class MaskingPatternBuilderTest { val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) val patterns = MaskingPatternBuilder().pathFields(pathFields).build() - assertEquals(pathFields.size, patterns.size) - assertAll( { assertTrue(patterns[0].pattern.pattern().contains("first")) }, { assertTrue(patterns[0].pattern.pattern().contains("second")) }, { assertTrue(patterns[1].pattern.pattern().contains("third1")) }, - { assertTrue(patterns[1].pattern.pattern().contains("as")) } + { assertTrue(patterns[1].pattern.pattern().contains("as")) }, ) } } @@ -105,24 +140,26 @@ class MaskingPatternBuilderTest { assertEquals(1, patterns.size) - val actual = patterns.first().replaceAll( + val actual = + patterns.first().replaceAll( + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + } + } + """.trimIndent(), + ) + val expected = """ - { - "globalField1": "value1", - "globalField2": { - "globalField1": "value2" - } - } - """.trimIndent() - ) - val expected = """ - { - "globalField1": "$OMITTED", - "globalField2": { - "globalField1": "$OMITTED" + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + } } - } - """.trimIndent() + """.trimIndent() assertEquals(expected, actual) } @@ -134,37 +171,39 @@ class MaskingPatternBuilderTest { assertEquals(pathFields.size, patterns.size) - var actual = """ - { - "first": { - "second": "value1" - }, - "first1": { - "second1": { - "third1": { - "as": "value2" + var actual = + """ + { + "first": { + "second": "value1" + }, + "first1": { + "second1": { + "third1": { + "as": "value2" + } } } } - } - """.trimIndent() + """.trimIndent() patterns.forEach { actual = it.replaceAll(actual) } - val expected = """ - { - "first": { - "second": "$OMITTED" - }, - "first1": { - "second1": { - "third1": { - "as": "$OMITTED" + val expected = + """ + { + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } } } } - } - """.trimIndent() + """.trimIndent() assertEquals(expected, actual) } @@ -174,52 +213,55 @@ class MaskingPatternBuilderTest { val globalFields = setOf("globalField1", "globalField2") val pathFields = setOf(listOf("first", "second"), listOf("first1", "second1", "third1", "as")) - val maskingPatterns = MaskingPatternBuilder() - .globalFields(globalFields) - .pathFields(pathFields) - .build() + val maskingPatterns = + MaskingPatternBuilder() + .globalFields(globalFields) + .pathFields(pathFields) + .build() assertEquals(pathFields.size + 1, maskingPatterns.size) - var actual = """ - { - "globalField1": "value1", - "globalField2": { - "globalField1": "value2" - }, - "first": { - "second": "value3" - }, - "first1": { - "second1": { - "third1": { - "as": "value4" + var actual = + """ + { + "globalField1": "value1", + "globalField2": { + "globalField1": "value2" + }, + "first": { + "second": "value3" + }, + "first1": { + "second1": { + "third1": { + "as": "value4" + } } } } - } - """.trimIndent() + """.trimIndent() maskingPatterns.forEach { actual = it.replaceAll(actual) } - val expected = """ - { - "globalField1": "$OMITTED", - "globalField2": { - "globalField1": "$OMITTED" - }, - "first": { - "second": "$OMITTED" - }, - "first1": { - "second1": { - "third1": { - "as": "$OMITTED" + val expected = + """ + { + "globalField1": "$OMITTED", + "globalField2": { + "globalField1": "$OMITTED" + }, + "first": { + "second": "$OMITTED" + }, + "first1": { + "second1": { + "third1": { + "as": "$OMITTED" + } } } } - } - """.trimIndent() + """.trimIndent() assertEquals(expected, actual) }