Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add log masking feature #138

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.expediagroup.sdk.core.common

internal interface Feature {
fun enable()
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<MaskingPattern>

/**
* Clears all existing masking patterns.
*/
fun clear()
}

/**
* Object implementing the PatternBasedMask interface.
*/
internal val mask = object : PatternBasedMask {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this to be an object? Couldn't we build and pass the masking configs somewhere?

val patterns: MutableList<MaskingPattern> = 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
}
}
Original file line number Diff line number Diff line change
@@ -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<String>
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<List<String>>
get() = emptySet()

override fun enable() {
mask.addPatternIfNotExists(
*MaskingPatternBuilder().apply {
globalFields(*globalMaskedFields.toTypedArray())
pathFields(*pathMaskedFields.toTypedArray())
}.build().toTypedArray()
)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<String> = setOf()
private var pathFields: Set<List<String>> = 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<String>) = apply {
pathFields += paths.map { it.takeLast(2) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we document this behavior in the method doc? we need to keep track of this workaround

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will this affect us? Didn't you mention you have a workaround for it?

And to make sure I understand, I wouldn't be able to mask x in a.b.c.x without masking a.x.y.z because of this limit. Right? Please add a clear example to the doc.

}

/**
* Builds the list of MaskingPattern based on the added global and path fields.
*
* @return A list of MaskingPattern.
*/
fun build(): List<MaskingPattern> = buildList {
/**
* Builds masking patterns for global fields.
*
* @return A list of MaskingPattern for global fields.
*/
fun buildGlobalFieldsMaskingPattern(): List<MaskingPattern> =
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<MaskingPattern> = 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())
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading