-
Notifications
You must be signed in to change notification settings - Fork 488
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* implement luhn loki processor * Apply suggestions from code review Co-authored-by: Clayton Cornell <[email protected]> * use paypal generated credit card in docs * finish implementing luhn filter, remove dead code * fix lint --------- Co-authored-by: Clayton Cornell <[email protected]> Co-authored-by: mattdurham <[email protected]>
- Loading branch information
1 parent
eb56b0f
commit 19b05d2
Showing
6 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package stages | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"time" | ||
"unicode" | ||
|
||
"github.com/prometheus/common/model" | ||
) | ||
|
||
// LuhnFilterConfig configures a processing stage that filters out Luhn-valid numbers. | ||
type LuhnFilterConfig struct { | ||
Replacement string `river:"replacement,attr,optional"` | ||
Source *string `river:"source,attr,optional"` | ||
MinLength int `river:"min_length,attr,optional"` | ||
} | ||
|
||
// validateLuhnFilterConfig validates the LuhnFilterConfig. | ||
func validateLuhnFilterConfig(c LuhnFilterConfig) error { | ||
if c.Replacement == "" { | ||
c.Replacement = "**REDACTED**" | ||
} | ||
if c.MinLength < 1 { | ||
c.MinLength = 13 | ||
} | ||
if c.Source != nil && *c.Source == "" { | ||
return ErrEmptyRegexStageSource | ||
} | ||
return nil | ||
} | ||
|
||
// newLuhnFilterStage creates a new LuhnFilterStage. | ||
func newLuhnFilterStage(config LuhnFilterConfig) (Stage, error) { | ||
if err := validateLuhnFilterConfig(config); err != nil { | ||
return nil, err | ||
} | ||
return toStage(&luhnFilterStage{ | ||
config: &config, | ||
}), nil | ||
} | ||
|
||
// luhnFilterStage applies Luhn algorithm filtering to log entries. | ||
type luhnFilterStage struct { | ||
config *LuhnFilterConfig | ||
} | ||
|
||
// Process implements Stage. | ||
func (r *luhnFilterStage) Process(labels model.LabelSet, extracted map[string]interface{}, t *time.Time, entry *string) { | ||
input := entry | ||
if r.config.Source != nil { | ||
value, ok := extracted[*r.config.Source] | ||
if !ok { | ||
return | ||
} | ||
strVal, ok := value.(string) | ||
if !ok { | ||
return | ||
} | ||
input = &strVal | ||
} | ||
|
||
if input == nil { | ||
return | ||
} | ||
|
||
// Replace Luhn-valid numbers in the input. | ||
updatedEntry := replaceLuhnValidNumbers(*input, r.config.Replacement, r.config.MinLength) | ||
*entry = updatedEntry | ||
} | ||
|
||
// replaceLuhnValidNumbers scans the input for Luhn-valid numbers and replaces them. | ||
|
||
func replaceLuhnValidNumbers(input, replacement string, minLength int) string { | ||
var sb strings.Builder | ||
var currentNumber strings.Builder | ||
|
||
flushNumber := func() { | ||
// If the number is at least minLength, check if it's a Luhn-valid number. | ||
if currentNumber.Len() >= minLength { | ||
numberStr := currentNumber.String() | ||
number, err := strconv.Atoi(numberStr) | ||
if err == nil && isLuhn(number) { | ||
// If the number is Luhn-valid, replace it. | ||
sb.WriteString(replacement) | ||
} else { | ||
// If the number is not Luhn-valid, write it as is. | ||
sb.WriteString(numberStr) | ||
} | ||
} else if currentNumber.Len() > 0 { | ||
// If the number is less than minLength but not empty, write it as is. | ||
sb.WriteString(currentNumber.String()) | ||
} | ||
// Reset the current number. | ||
currentNumber.Reset() | ||
} | ||
|
||
// Iterate over the input, replacing Luhn-valid numbers. | ||
for _, char := range input { | ||
// If the character is a digit, add it to the current number. | ||
if unicode.IsDigit(char) { | ||
currentNumber.WriteRune(char) | ||
} else { | ||
// If the character is not a digit, flush the current number and write the character. | ||
flushNumber() | ||
sb.WriteRune(char) | ||
} | ||
} | ||
flushNumber() // Ensure any trailing number is processed | ||
|
||
return sb.String() | ||
} | ||
|
||
// isLuhn check number is valid or not based on Luhn algorithm | ||
func isLuhn(number int) bool { | ||
// Luhn algorithm is a simple checksum formula used to validate a | ||
// variety of identification numbers, such as credit card numbers, IMEI | ||
// numbers, National Provider Identifier numbers in the US, and | ||
// Canadian Social Insurance Numbers. This is a simple implementation | ||
// of the Luhn algorithm. | ||
// https://en.wikipedia.org/wiki/Luhn_algorithm | ||
return (number%10+checksum(number/10))%10 == 0 | ||
} | ||
|
||
func checksum(number int) int { | ||
var luhn int | ||
|
||
for i := 0; number > 0; i++ { | ||
cur := number % 10 | ||
|
||
if i%2 == 0 { // even | ||
cur *= 2 | ||
if cur > 9 { | ||
cur = cur%10 + cur/10 | ||
} | ||
} | ||
|
||
luhn += cur | ||
number /= 10 | ||
} | ||
return luhn % 10 | ||
} | ||
|
||
// Name implements Stage. | ||
func (r *luhnFilterStage) Name() string { | ||
return StageTypeLuhn | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package stages | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
// Test cases for the Luhn algorithm validation | ||
func TestIsLuhnValid(t *testing.T) { | ||
cases := []struct { | ||
input int | ||
want bool | ||
}{ | ||
{4539_1488_0343_6467, true}, // Valid Luhn number | ||
{1234_5678_1234_5670, true}, // Another valid Luhn number | ||
{499_2739_8112_1717, false}, // Invalid Luhn number | ||
{1234567812345678, false}, // Another invalid Luhn number | ||
{3782_822463_10005, true}, // Short, valid Luhn number | ||
{123, false}, // Short, invalid Luhn number | ||
} | ||
|
||
for _, c := range cases { | ||
got := isLuhn(c.input) | ||
if got != c.want { | ||
t.Errorf("isLuhnValid(%q) == %t, want %t", c.input, got, c.want) | ||
} | ||
} | ||
} | ||
|
||
// TestReplaceLuhnValidNumbers tests the replaceLuhnValidNumbers function. | ||
func TestReplaceLuhnValidNumbers(t *testing.T) { | ||
cases := []struct { | ||
input string | ||
replacement string | ||
want string | ||
}{ | ||
// Test case with a single Luhn-valid number | ||
{"My credit card number is 3530111333300000.", "**REDACTED**", "My credit card number is **REDACTED**."}, | ||
// Test case with multiple Luhn-valid numbers | ||
{"Cards 4532015112830366 and 6011111111111117 are valid.", "**REDACTED**", "Cards **REDACTED** and **REDACTED** are valid."}, | ||
// Test case with no Luhn-valid numbers | ||
{"No valid numbers here.", "**REDACTED**", "No valid numbers here."}, | ||
// Test case with mixed content | ||
{"Valid: 4556737586899855, invalid: 1234.", "**REDACTED**", "Valid: **REDACTED**, invalid: 1234."}, | ||
// Test case with edge cases | ||
{"Edge cases: 0, 00, 000, 1.", "**REDACTED**", "Edge cases: 0, 00, 000, 1."}, | ||
} | ||
|
||
for _, c := range cases { | ||
got := replaceLuhnValidNumbers(c.input, c.replacement, 13) | ||
if got != c.want { | ||
t.Errorf("replaceLuhnValidNumbers(%q, %q) == %q, want %q", c.input, c.replacement, got, c.want) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters